friendlist.c 29 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;
}
734
735
736
737
738
739
740
741
742
743
744
745
746
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
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833

int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *list, const char *vcard_file) {
	MSList *vcards = linphone_vcard_list_from_vcard4_file(vcard_file);
	int count = 0;
	
#ifndef VCARD_ENABLED
	ms_error("vCard support wasn't enabled at compilation time");
	return -1;
#endif
	if (!vcards) {
		ms_error("Failed to parse the file %s", vcard_file);
		return -1;
	}
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
	
	while (vcards != NULL && vcards->data != NULL) {
		LinphoneVCard *vcard = (LinphoneVCard *)vcards->data;
		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);
		}
		vcards = ms_list_next(vcards);
	}
#ifndef FRIENDS_SQL_STORAGE_ENABLED
	linphone_core_write_friends_config(list->lc);
#endif
	return count;
}

int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *list, const char *vcard_buffer) {
	MSList *vcards = linphone_vcard_list_from_vcard4_buffer(vcard_buffer);
	int count = 0;
	
#ifndef VCARD_ENABLED
	ms_error("vCard support wasn't enabled at compilation time");
	return -1;
#endif
	if (!vcards) {
		ms_error("Failed to parse the buffer");
		return -1;
	}
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
	
	while (vcards != NULL && vcards->data != NULL) {
		LinphoneVCard *vcard = (LinphoneVCard *)vcards->data;
		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);
		}
		vcards = ms_list_next(vcards);
	}
#ifndef FRIENDS_SQL_STORAGE_ENABLED
	linphone_core_write_friends_config(list->lc);
#endif
	return count;
}

void linphone_friend_list_export_friends_as_vcard4_file(LinphoneFriendList *list, const char *vcard_file) {
	FILE *file = NULL;
	const MSList *friends = linphone_friend_list_get_friends(list);
	
	file = fopen(vcard_file, "w");
	if (file == NULL) {
		ms_warning("Could not write %s ! Maybe it is read-only. Contacts will not be saved.", vcard_file);
		return;
	}
	
#ifndef VCARD_ENABLED
	ms_error("vCard support wasn't enabled at compilation time");
#endif
	while (friends != NULL && friends->data != NULL) {
		LinphoneFriend *lf = (LinphoneFriend *)friends->data;
		LinphoneVCard *vcard = linphone_friend_get_vcard(lf);
		if (vcard) {
			const char *vcard_text = linphone_vcard_as_vcard4_string(vcard);
			fprintf(file, "%s", vcard_text);
		} else {
			ms_warning("Couldn't export friend %s because it doesn't have a vCard attached", linphone_address_as_string(linphone_friend_get_address(lf)));
		}
		friends = ms_list_next(friends);
	}
	
	fclose(file);
}