friendlist.c 37 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
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
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 19
*/

20
#include "linphone/core.h"
21 22
#include "private.h"

johan's avatar
johan committed
23
#include <bctoolbox/crypto.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
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;
}

59
LinphoneFriendListCbsContactCreatedCb linphone_friend_list_cbs_get_contact_created(const LinphoneFriendListCbs *cbs) {
60 61 62
	return cbs->contact_created_cb;
}

63
void linphone_friend_list_cbs_set_contact_created(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactCreatedCb cb) {
64 65 66
	cbs->contact_created_cb = cb;
}

67
LinphoneFriendListCbsContactDeletedCb linphone_friend_list_cbs_get_contact_deleted(const LinphoneFriendListCbs *cbs) {
68 69 70
	return cbs->contact_deleted_cb;
}

71
void linphone_friend_list_cbs_set_contact_deleted(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactDeletedCb cb) {
72 73
	cbs->contact_deleted_cb = cb;
}
74

75
LinphoneFriendListCbsContactUpdatedCb linphone_friend_list_cbs_get_contact_updated(const LinphoneFriendListCbs *cbs) {
76 77 78
	return cbs->contact_updated_cb;
}

79
void linphone_friend_list_cbs_set_contact_updated(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactUpdatedCb cb) {
80 81 82
	cbs->contact_updated_cb = cb;
}

83
LinphoneFriendListCbsSyncStateChangedCb linphone_friend_list_cbs_get_sync_status_changed(const LinphoneFriendListCbs *cbs) {
84 85 86
	return cbs->sync_state_changed_cb;
}

87
void linphone_friend_list_cbs_set_sync_status_changed(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsSyncStateChangedCb cb) {
88 89 90
	cbs->sync_state_changed_cb = cb;
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104
static int add_uri_entry(xmlTextWriterPtr writer, int err, const char *uri) {
	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);
	}
	return err;
}

Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
105 106 107 108 109 110
static bctbx_list_t * uri_list(const LinphoneFriendList *list) {
	bctbx_list_t * uri_list = NULL;
	bctbx_list_t * elem = NULL;
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
		bctbx_list_t *iterator;
111
		const bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
112
		bctbx_list_t *numbers = linphone_friend_get_phone_numbers(lf);
113
		iterator = (bctbx_list_t *)addresses;
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		while (iterator) {
			LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
			if (addr) {
				char *uri = linphone_address_as_string_uri_only(addr);
				if (uri) {
					if (bctbx_list_find_custom(uri_list, (bctbx_compare_func)strcmp, uri) == NULL) {
						uri_list =  bctbx_list_insert_sorted(uri_list, uri, (bctbx_compare_func)strcasecmp);
					}
				}
			}
			iterator = bctbx_list_next(iterator);
		}
		iterator = numbers;
		while (iterator) {
			const char *number = (const char *)bctbx_list_get_data(iterator);
			const char *uri = linphone_friend_phone_number_to_sip_uri(lf, number);
			if (uri) {
				if (bctbx_list_find_custom(uri_list, (bctbx_compare_func)strcmp, uri) == NULL) {
					uri_list = bctbx_list_insert_sorted(uri_list, ms_strdup(uri), (bctbx_compare_func)strcasecmp);
				}
			}
			iterator = bctbx_list_next(iterator);
		}
	}
	return uri_list;
}

141 142 143 144 145 146
static char * create_resource_list_xml(const LinphoneFriendList *list) {
	char *xml_content = NULL;
	xmlBufferPtr buf;
	xmlTextWriterPtr writer;
	int err;

147
	if (bctbx_list_size(list->friends) <= 0) return NULL;
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

	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");
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
173 174 175

	{
		bctbx_list_t* entries = uri_list(list);
176 177
		bctbx_list_t* it;
		for(it = entries; it != NULL; it = it->next){
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
178
			err = add_uri_entry(writer, err, it->data);
179
		}
180
		bctbx_list_free_with_data(entries, ms_free);
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
	}
	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;
}

204 205 206 207 208 209
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];
210
		LinphoneFriend *lf;
211 212
		LinphoneContent *presence_part;
		xmlXPathObjectPtr resource_object;
213 214
		const char *version_str = NULL;
		const char *full_state_str = NULL;
215
		const char *uri = NULL;
216 217
		bool_t full_state = FALSE;
		int version;
218 219 220 221
		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");
222 223 224 225 226 227 228

		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
229
		linphone_free_xml_text_content(version_str);
230 231
		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);
232
			linphone_friend_list_update_subscriptions(list); /* Refresh subscription to get new full state notify. */
233 234 235 236 237 238 239 240 241
			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)) {
242
			bctbx_list_t *l = list->friends;
243 244
			for (; l != NULL; l = bctbx_list_next(l)) {
				lf = (LinphoneFriend *)bctbx_list_get_data(l);
245
				linphone_friend_clear_presence_models(lf);
246 247 248 249 250 251 252 253 254 255
			}
			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;

256 257 258
		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++) {
259
				LinphoneAddress* addr;
260 261 262
				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;
263
				addr = linphone_address_new(uri);
Simon Morlat's avatar
Simon Morlat committed
264 265
				if (!addr) continue;
				lf = linphone_friend_list_find_friend_by_address(list, addr);
266
				linphone_address_unref(addr);
267
				if (lf != NULL) {
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
					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) {
283
									const char *phone_number = linphone_friend_sip_uri_to_phone_number(lf, uri);
284
									lf->presence_received = TRUE;
285
									if (phone_number) linphone_friend_set_presence_model_for_uri_or_tel(lf, phone_number, (LinphonePresenceModel *)presence);
286
									else linphone_friend_set_presence_model_for_uri_or_tel(lf, uri, (LinphonePresenceModel *)presence);
287
									if (full_state == FALSE) {
288 289
										if (phone_number)
											linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, phone_number, (LinphonePresenceModel *)presence);
290 291
										else
											linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, uri, (LinphonePresenceModel *)presence);
292
										linphone_core_notify_notify_presence_received(list->lc, lf);
293
									}
294
								}
Ghislain MARY's avatar
Ghislain MARY committed
295
								linphone_content_unref(presence_part);
296 297 298 299 300
							}
						}
						if (cid != NULL) linphone_free_xml_text_content(cid);
					}
					if (state != NULL) linphone_free_xml_text_content(state);
301
					lf->subscribe_active = TRUE;
302 303 304 305 306
				}
				linphone_free_xml_text_content(uri);
			}
		}
		if (resource_object != NULL) xmlXPathFreeObject(resource_object);
307 308

		if (full_state == TRUE) {
309
			const bctbx_list_t *addresses;
310 311
			bctbx_list_t *numbers;
			bctbx_list_t *iterator;
312
			bctbx_list_t *l = list->friends;
313 314
			for (; l != NULL; l = bctbx_list_next(l)) {
				lf = (LinphoneFriend *)bctbx_list_get_data(l);
315 316
				addresses = linphone_friend_get_addresses(lf);
				numbers = linphone_friend_get_phone_numbers(lf);
317
				iterator = (bctbx_list_t *)addresses;
318 319 320 321
				while (iterator) {
					LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
					char *uri = linphone_address_as_string_uri_only(addr);
					const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, uri);
322
					if (presence) linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, uri, presence);
323 324 325 326 327 328 329
					ms_free(uri);
					iterator = bctbx_list_next(iterator);
				}
				iterator = numbers;
				while (iterator) {
					const char *number = (const char *)bctbx_list_get_data(iterator);
					const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, number);
330
					if (presence) linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, number, presence);
331 332
					iterator = bctbx_list_next(iterator);
				}
Ghislain MARY's avatar
Ghislain MARY committed
333
				if (numbers) bctbx_list_free(numbers);
334 335
				if (linphone_friend_is_presence_received(lf) == TRUE) {
					linphone_core_notify_notify_presence_received(list->lc, lf);
336 337 338
				}
			}
		}
339 340 341 342 343 344 345 346
	} else {
		ms_warning("Wrongly formatted rlmi+xml body: %s", xml_ctx->errorBuffer);
	}

end:
	linphone_xmlparsing_context_destroy(xml_ctx);
}

347
static bool_t linphone_friend_list_has_subscribe_inactive(const LinphoneFriendList *list) {
348
	bctbx_list_t *l = list->friends;
349
	bool_t has_subscribe_inactive = FALSE;
350 351
	for (; l != NULL; l = bctbx_list_next(l)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(l);
352
		if (lf->subscribe_active != TRUE) {
353 354 355 356 357 358 359
			has_subscribe_inactive = TRUE;
			break;
		}
	}
	return has_subscribe_inactive;
}

360 361
static LinphoneFriendList * linphone_friend_list_new(void) {
	LinphoneFriendList *list = belle_sip_object_new(LinphoneFriendList);
362
	list->cbs = linphone_friend_list_cbs_new();
363
	list->enable_subscriptions = TRUE;
364 365 366
	return list;
}

367 368
static void linphone_friend_list_destroy(LinphoneFriendList *list) {
	if (list->display_name != NULL) ms_free(list->display_name);
369
	if (list->rls_addr) linphone_address_unref(list->rls_addr);
370
	if (list->rls_uri != NULL) ms_free(list->rls_uri);
371
	if (list->content_digest != NULL) ms_free(list->content_digest);
372 373 374
	if (list->event != NULL) {
		linphone_event_terminate(list->event);
		linphone_event_unref(list->event);
375
		list->event = NULL;
376
	}
377 378
	if (list->uri != NULL) ms_free(list->uri);
	if (list->cbs) linphone_friend_list_cbs_unref(list->cbs);
379 380
	if (list->dirty_friends_to_update) list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
	if (list->friends) list->friends = bctbx_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
381 382 383 384 385 386 387 388
}

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
389
	FALSE
390 391 392
);


393 394 395
LinphoneFriendList * linphone_core_create_friend_list(LinphoneCore *lc) {
	LinphoneFriendList *list = linphone_friend_list_new();
	list->lc = lc;
396 397 398 399 400 401 402 403
	return list;
}

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

404 405 406
void _linphone_friend_list_release(LinphoneFriendList *list){
	/*drops all references to core and unref*/
	list->lc = NULL;
Simon Morlat's avatar
Simon Morlat committed
407 408 409 410
	if (list->event != NULL) {
		linphone_event_unref(list->event);
		list->event = NULL;
	}
411 412 413 414
	if (list->cbs) {
		linphone_friend_list_cbs_unref(list->cbs);
		list->cbs = NULL;
	}
415
	if (list->dirty_friends_to_update) {
416
		list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
417 418
	}
	if (list->friends) {
419
		list->friends = bctbx_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
420
	}
421
	linphone_friend_list_unref(list);
422 423
}

424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
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);
447
		linphone_core_store_friends_list_in_db(list->lc, list);
448 449 450
	}
}

451 452
const LinphoneAddress * linphone_friend_list_get_rls_address(const LinphoneFriendList *list){
	return list->rls_addr;
453
}
454 455 456 457 458 459 460 461
const LinphoneAddress * _linphone_friend_list_get_rls_address(const LinphoneFriendList *list) {
	if (list->rls_addr)
		return list->rls_addr;
	else if (list->lc)
		return list->lc->default_rls_addr;
	else
		return NULL;
}
462 463 464 465 466 467 468 469
void linphone_friend_list_set_rls_address(LinphoneFriendList *list, const LinphoneAddress *rls_addr){
	LinphoneAddress *new_rls_addr = rls_addr ? linphone_address_clone(rls_addr) : NULL;
	
	if (list->rls_addr){
		linphone_address_unref(list->rls_addr);
	}
	list->rls_addr = new_rls_addr;
	if (list->rls_uri != NULL){
470 471 472
		ms_free(list->rls_uri);
		list->rls_uri = NULL;
	}
473 474
	if (list->rls_addr){
		list->rls_uri = linphone_address_as_string(list->rls_addr);
475
		linphone_core_store_friends_list_in_db(list->lc, list);
476 477 478
	}
}

479 480 481 482 483 484 485
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) {
	LinphoneAddress *addr = rls_uri ? linphone_core_create_address(list->lc, rls_uri) : NULL;
	linphone_friend_list_set_rls_address(list, addr);
Simon Morlat's avatar
Simon Morlat committed
486
	if (addr) linphone_address_unref(addr);
487 488
}

489
static LinphoneFriendListStatus _linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
490
	LinphoneFriendListStatus status = LinphoneFriendListInvalidFriend;
491
	const LinphoneAddress *addr;
492

493
	if (!list || lf->friend_list) {
Sylvain Berfini's avatar
Sylvain Berfini committed
494 495
		if (!list)
			ms_error("linphone_friend_list_add_friend(): invalid list, null");
496
		if (lf->friend_list)
497
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
498
		return status;
Simon Morlat's avatar
Simon Morlat committed
499
	}
500
	addr = linphone_friend_get_address(lf);
501
	if (bctbx_list_find(list->friends, lf) != NULL) {
502 503 504
		char *tmp = NULL;
		if (addr) tmp = linphone_address_as_string(addr);
		ms_warning("Friend %s already in list [%s], ignored.", tmp ? tmp : "unknown", list->display_name);
Ghislain MARY's avatar
Ghislain MARY committed
505
		if (tmp) ms_free(tmp);
506
	} else {
507
		status = linphone_friend_list_import_friend(list, lf, synchronize);
508
		linphone_friend_save(lf, lf->lc);
509
	}
510

511 512 513 514 515
	if (list->rls_uri == NULL) {
		/* Mimic the behaviour of linphone_core_add_friend() when a resource list server is not in use */
		linphone_friend_apply(lf, lf->lc);
	}
	return status;
516 517
}

518 519 520 521 522 523 524 525
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);
}

526
LinphoneFriendListStatus linphone_friend_list_import_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
527
	if (lf->friend_list) {
528 529
		if (lf->friend_list)
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
Sylvain Berfini's avatar
Sylvain Berfini committed
530 531
		return LinphoneFriendListInvalidFriend;
	}
532
	lf->friend_list = list;
533
	lf->lc = list->lc;
534
	list->friends = bctbx_list_append(list->friends, linphone_friend_ref(lf));
535
	if (synchronize) {
536
		list->dirty_friends_to_update = bctbx_list_append(list->dirty_friends_to_update, linphone_friend_ref(lf));
537
	}
538 539 540
	return LinphoneFriendListOK;
}

541
static void carddav_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {
542 543 544
	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);
	}
545 546 547
	linphone_carddav_context_destroy(cdc);
}

548
static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t remove_from_server) {
549
	bctbx_list_t *elem = bctbx_list_find(list->friends, lf);
550
	if (elem == NULL) return LinphoneFriendListNonExistentFriend;
551

552
#ifdef SQLITE_STORAGE_ENABLED
553 554 555
	if (lf && lf->lc && lf->lc->friends_db) {
		linphone_core_remove_friend_from_db(lf->lc, lf);
	}
556
#endif
557
	if (remove_from_server) {
558
		LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
559 560
		if (lvc && linphone_vcard_get_uid(lvc)) {
			LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
561 562 563 564 565 566
			if (cdc) {
				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);
567 568
			}
		}
569
	}
570 571 572
	if (!list->lc->friends_db_file) {
		linphone_core_write_friends_config(list->lc);
	}
573

574 575
	lf->friend_list = NULL;
	linphone_friend_unref(lf);
576
	list->friends = bctbx_list_erase_link(list->friends, elem);
577
	return LinphoneFriendListOK;
578
}
579

580 581 582 583
LinphoneFriendListStatus linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_remove_friend(list, lf, TRUE);
}

584
const bctbx_list_t * linphone_friend_list_get_friends(const LinphoneFriendList *list) {
585 586 587
	return list->friends;
}

588
void linphone_friend_list_update_dirty_friends(LinphoneFriendList *list) {
589
	bctbx_list_t *dirty_friends = list->dirty_friends_to_update;
590

591 592 593
	while (dirty_friends) {
		LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
		if (cdc) {
594
			LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(dirty_friends);
595
			cdc->sync_done_cb = carddav_done;
596
			if (lf) {
597 598 599
				if (cdc->friend_list->cbs->sync_state_changed_cb) {
					cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
				}
600 601 602
				linphone_carddav_put_vcard(cdc, lf);
			}
		}
603
		dirty_friends = bctbx_list_next(dirty_friends);
604
	}
605
	list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
606 607 608
}

static void carddav_created(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
609
	if (cdc) {
610
		LinphoneFriendList *lfl = cdc->friend_list;
611
		linphone_friend_list_import_friend(lfl, lf, FALSE);
612
		if (cdc->friend_list->cbs->contact_created_cb) {
613
			cdc->friend_list->cbs->contact_created_cb(lfl, lf);
614
		}
615 616 617 618
	}
}

static void carddav_removed(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
619
	if (cdc) {
620
		LinphoneFriendList *lfl = cdc->friend_list;
621
		_linphone_friend_list_remove_friend(lfl, lf, FALSE);
622
		if (cdc->friend_list->cbs->contact_deleted_cb) {
623
			cdc->friend_list->cbs->contact_deleted_cb(lfl, lf);
624
		}
625 626 627
	}
}

628
static void carddav_updated(LinphoneCardDavContext *cdc, LinphoneFriend *lf_new, LinphoneFriend *lf_old) {
629 630
	if (cdc) {
		LinphoneFriendList *lfl = cdc->friend_list;
631
		bctbx_list_t *elem = bctbx_list_find(lfl->friends, lf_old);
632
		if (elem) {
633
			elem->data = linphone_friend_ref(lf_new);
634
		}
635
		linphone_core_store_friend_in_db(lf_new->lc, lf_new);
636

637 638 639
		if (cdc->friend_list->cbs->contact_updated_cb) {
			cdc->friend_list->cbs->contact_updated_cb(lfl, lf_new, lf_old);
		}
640
		linphone_friend_unref(lf_old);
641
	}
642 643
}

644
void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *list) {
645
	LinphoneCardDavContext *cdc = NULL;
646

647 648 649 650
	if (!list || !list->uri || !list->lc) {
		ms_error("FATAL");
		return;
	}
651

652
	cdc = linphone_carddav_context_new(list);
653 654 655
	if (cdc) {
		cdc->contact_created_cb = carddav_created;
		cdc->contact_removed_cb = carddav_removed;
656
		cdc->contact_updated_cb = carddav_updated;
657
		cdc->sync_done_cb = carddav_done;
658 659 660
		if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) {
			cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
		}
661 662 663 664
		linphone_carddav_synchronize(cdc);
	}
}

665
LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address) {
666
	LinphoneFriend *result = NULL;
667
	const bctbx_list_t *elem;
668
	const char *param = linphone_address_get_uri_param(address, "user");
669
	bool_t find_phone_number = (param && (strcmp(param, "phone") == 0));
670

671 672
	for (elem = list->friends; (elem != NULL) && (result == NULL); elem = bctbx_list_next(elem)) {
		bctbx_list_t *iterator;
673
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
674 675 676 677 678 679
		if (find_phone_number == TRUE) {
			char *uri = linphone_address_as_string_uri_only(address);
			const char *phone_number = linphone_friend_sip_uri_to_phone_number(lf, uri);
			bctbx_list_t *phone_numbers = linphone_friend_get_phone_numbers(lf);
			iterator = phone_numbers;
			ms_free(uri);
680
			if (!phone_number) continue;
681 682 683 684 685 686
			while (iterator && (result == NULL)) {
				const char *number = (const char *)bctbx_list_get_data(iterator);
				if (strcmp(number, phone_number) == 0) result = lf;
				iterator = bctbx_list_next(iterator);
			}
		} else {
687 688
			const bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
			iterator = (bctbx_list_t *)addresses;
689 690 691 692 693
			while (iterator && (result == NULL)) {
				LinphoneAddress *lfaddr = (LinphoneAddress *)bctbx_list_get_data(iterator);
				if (linphone_address_weak_equal(lfaddr, address)) result = lf;
				iterator = bctbx_list_next(iterator);
			}
694
		}
695
	}
696
	return result;
697 698 699 700
}

LinphoneFriend * linphone_friend_list_find_friend_by_uri(const LinphoneFriendList *list, const char *uri) {
	LinphoneAddress *address = linphone_address_new(uri);
701
	LinphoneFriend *lf = address ? linphone_friend_list_find_friend_by_address(list, address) : NULL;
702
	if (address) linphone_address_unref(address);
703
	return lf;
704 705 706
}

LinphoneFriend * linphone_friend_list_find_friend_by_ref_key(const LinphoneFriendList *list, const char *ref_key) {
707
	const bctbx_list_t *elem;
708
	if (ref_key == NULL) return NULL;
709
	if (list == NULL) return NULL;
710 711
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
712
		if ((lf->refkey != NULL) && (strcmp(lf->refkey, ref_key) == 0)) return lf;
713 714 715 716 717
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_inc_subscribe(const LinphoneFriendList *list, SalOp *op) {
718
	const bctbx_list_t *elem;
719 720
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
721
		if (bctbx_list_find(lf->insubs, op)) return lf;
722 723 724 725 726
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_out_subscribe(const LinphoneFriendList *list, SalOp *op) {
727
	const bctbx_list_t *elem;
728 729
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
730
		if (lf->outsub && ((lf->outsub == op) || sal_op_is_forked_of(lf->outsub, op))) return lf;
731 732 733 734
	}
	return NULL;
}

735 736
static void linphone_friend_list_close_subscriptions(LinphoneFriendList *list) {
	/* FIXME we should wait until subscription to complete. */
jehan's avatar
jehan committed
737 738
	if (list->event) {
		linphone_event_terminate(list->event);
739 740
		linphone_event_unref(list->event);
		list->event = NULL;
741
	}
742
	bctbx_list_for_each(list->friends, (void (*)(void *))linphone_friend_close_subscriptions);
743 744
}

745
static void linphone_friend_list_send_list_subscription(LinphoneFriendList *list){
746
	const LinphoneAddress *address = _linphone_friend_list_get_rls_address(list);
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
	char *xml_content = create_resource_list_xml(list);
	if ((address != NULL) && (xml_content != NULL) && (linphone_friend_list_has_subscribe_inactive(list) == TRUE)) {
		unsigned char digest[16];
		bctbx_md5((unsigned char *)xml_content, strlen(xml_content), digest);
		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_ref(list->event);
			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);
			if (linphone_core_content_encoding_supported(list->lc, "deflate")) {
				linphone_content_set_encoding(content, "deflate");
				linphone_event_add_custom_header(list->event, "Accept-Encoding", "deflate");
			}
			linphone_event_send_subscribe(list->event, content);
			linphone_content_unref(content);
			linphone_event_set_user_data(list->event, list);
		}
	}
	if (xml_content != NULL) ms_free(xml_content);
}

void linphone_friend_list_update_subscriptions(LinphoneFriendList *list){
	LinphoneProxyConfig *cfg = NULL;
790
	const LinphoneAddress *address = _linphone_friend_list_get_rls_address(list);
791 792 793 794 795 796 797 798 799 800
	bool_t only_when_registered = FALSE;
	bool_t should_send_list_subscribe = FALSE;
	
	if (list->lc){
		if (address)
			cfg = linphone_core_lookup_known_proxy(list->lc, address);
		only_when_registered = linphone_core_should_subscribe_friends_only_when_registered(list->lc);
		should_send_list_subscribe = (!only_when_registered || !cfg || cfg->state == LinphoneRegistrationOk);
	}
	
801
	if (address != NULL) {
802
		if (list->enable_subscriptions) {
803 804 805 806 807 808 809 810 811 812
			if (should_send_list_subscribe){
				linphone_friend_list_send_list_subscription(list);
			}else{
				if (list->event){
					linphone_event_terminate(list->event);
					linphone_event_unref(list->event);
					list->event = NULL;
					ms_message("Friends list [%p] subscription terminated because proxy config lost connection", list);
				}else{
					ms_message("Friends list [%p] subscription update skipped since dependant proxy config is not yet registered", list);
813
				}
814
			}
815 816
		} else {
			ms_message("Friends list [%p] subscription update skipped since subscriptions not enabled yet", list);
817
		}
818
	} else if (list->enable_subscriptions) {
819
		const bctbx_list_t *elem;
820 821
		for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
			LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
822
			linphone_friend_update_subscribes(lf, only_when_registered);
823
		}
824 825 826 827
	}
}

void linphone_friend_list_invalidate_subscriptions(LinphoneFriendList *list) {
828
	const bctbx_list_t *elem;
829 830
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
831
		linphone_friend_invalidate_subscription(lf);
832 833 834 835
	}
}

void linphone_friend_list_notify_presence(LinphoneFriendList *list, LinphonePresenceModel *presence) {
836
	const bctbx_list_t *elem;
837 838
	for(elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
839
		linphone_friend_notify(lf, presence);
840 841
	}
}
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863

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
864
			linphone_content_unref(first_part);
865 866 867 868
			return;
		}

		linphone_friend_list_parse_multipart_related_body(list, body, linphone_content_get_string_buffer(first_part));
Ghislain MARY's avatar
Ghislain MARY committed
869
		linphone_content_unref(first_part);
870 871
	}
}
872 873 874 875 876 877 878 879 880 881 882 883

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);
884
		linphone_core_store_friends_list_in_db(list->lc, list);
885 886 887 888 889
	}
}

void linphone_friend_list_update_revision(LinphoneFriendList *list, int rev) {
	list->revision = rev;
890
	linphone_core_store_friends_list_in_db(list->lc, list);
891 892
}

893 894 895 896 897 898 899 900 901 902 903
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);
904

905
		if (state == LinphoneSubscriptionOutgoingProgress && linphone_event_get_reason(lev) == LinphoneReasonNoMatch) {
906
			ms_message("Reseting version count for friend list [%p]",list);
907 908 909 910
			list->expected_notification_version = 0;
		}
	}
}
911

912
LinphoneCore* linphone_friend_list_get_core(const LinphoneFriendList *list) {
913 914
	return list->lc;
}
915 916

int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *list, const char *vcard_file) {
917 918
	bctbx_list_t *vcards = NULL;
	bctbx_list_t *vcards_iterator = NULL;
919
	int count = 0;
920

921 922 923 924
	if (!linphone_core_vcard_supported()) {
		ms_error("vCard support wasn't enabled at compilation time");
		return -1;
	}
925 926 927 928
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
929

930
	vcards = linphone_vcard_context_get_vcard_list_from_file(list->lc->vcard_context, vcard_file);
931 932 933 934 935
	vcards_iterator = vcards;
	if (!vcards) {
		ms_error("Failed to parse the file %s", vcard_file);
		return -1;
	}
936

937 938
	while (vcards_iterator != NULL && bctbx_list_get_data(vcards_iterator) != NULL) {
		LinphoneVcard *vcard = (LinphoneVcard *)bctbx_list_get_data(vcards_iterator);
939 940 941
		LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard);
		if (lf) {
			if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) {
942
				linphone_friend_save(lf, lf->lc);
943 944 945 946 947 948
				count++;
			}
			linphone_friend_unref(lf);
		} else {
			linphone_vcard_free(vcard);
		}
949
		vcards_iterator = bctbx_list_next(vcards_iterator);
950
	}
951
	bctbx_list_free(vcards);
952
	linphone_core_store_friends_list_in_db(list->lc, list);
953 954 955 956
	return count;
}

int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *list, const char *vcard_buffer) {
957 958
	bctbx_list_t *vcards = NULL;
	bctbx_list_t *vcards_iterator = NULL;
959
	int count = 0;
960

961 962 963 964
	if (!linphone_core_vcard_supported()) {
		ms_error("vCard support wasn't enabled at compilation time");
		return -1;
	}
965 966 967 968
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
969

970
	vcards = linphone_vcard_context_get_vcard_list_from_buffer(list->lc->vcard_context, vcard_buffer);
971 972 973 974 975
	vcards_iterator = vcards;
	if (!vcards) {
		ms_error("Failed to parse the buffer");
		return -1;
	}
976

977 978
	while (vcards_iterator != NULL && bctbx_list_get_data(vcards_iterator) != NULL) {
		LinphoneVcard *vcard = (LinphoneVcard *)bctbx_list_get_data(vcards_iterator);
979 980 981 982 983 984 985 986 987
		LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard);
		if (lf) {
			if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) {
				count++;
			}
			linphone_friend_unref(lf);
		} else {
			linphone_vcard_free(vcard);
		}
988
		vcards_iterator = bctbx_list_next(vcards_iterator);
989
	}
990
	bctbx_list_free(vcards);
991
	linphone_core_store_friends_list_in_db(list->lc, list);
992 993 994 995 996
	return count;
}

void linphone_friend_list_export_friends_as_vcard4_file(LinphoneFriendList *list, const char *vcard_file) {
	FILE *file = NULL;
997 998 999 1000 1001 1002
	const bctbx_list_t *friends;

	if (!linphone_core_vcard_supported()) {
		ms_error("vCard support wasn't enabled at compilation time");
		return;
	}
1003

1004
	file = fopen(vcard_file, "wb");
1005 1006 1007 1008
	if (file == NULL) {
		ms_warning("Could not write %s ! Maybe it is read-only. Contacts will not be saved.", vcard_file);
		return;
	}
1009

1010 1011 1012
	friends = linphone_friend_list_get_friends(list);
	while (friends != NULL && bctbx_list_get_data(friends) != NULL) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(friends);
1013
		LinphoneVcard *vcard = linphone_friend_get_vcard(lf);
1014 1015 1016 1017
		if (vcard) {
			const char *vcard_text = linphone_vcard_as_vcard4_string(vcard);
			fprintf(file, "%s", vcard_text);
		}
1018
		friends = bctbx_list_next(friends);
1019
	}
1020

1021 1022
	fclose(file);
}
1023 1024 1025

void linphone_friend_list_enable_subscriptions(LinphoneFriendList *list, bool_t enabled) {
	if (list->enable_subscriptions != enabled) {
1026
		list->enable_subscriptions = enabled;
1027
		if (enabled) {
1028
			linphone_friend_list_update_subscriptions(list);
1029 1030 1031
		} else {
			linphone_friend_list_close_subscriptions(list);
		}
1032
		
1033 1034
	}
}