magic-search.cpp 17.2 KB
Newer Older
Erwan Croze's avatar
Erwan Croze committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * magic-search.cpp
 * Copyright (C) 2010-2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "magic-search-p.h"

#include <bctoolbox/list.h>
23
#include <algorithm>
Erwan Croze's avatar
Erwan Croze committed
24 25 26 27 28

#include "c-wrapper/internal/c-tools.h"
#include "linphone/utils/utils.h"
#include "linphone/core.h"
#include "linphone/types.h"
29
#include "logger/logger.h"
Erwan Croze's avatar
Erwan Croze committed
30 31 32 33 34 35 36 37 38 39
#include "private.h"

using namespace std;

LINPHONE_BEGIN_NAMESPACE

MagicSearch::MagicSearch(const std::shared_ptr<Core> &core) : CoreAccessor(core), Object(*new MagicSearchPrivate){
	L_D();
	d->mMinWeight = 0;
	d->mMaxWeight = 1000;
Erwan Croze's avatar
Erwan Croze committed
40
	d->mSearchLimit = 30;
Erwan Croze's avatar
Erwan Croze committed
41 42
	d->mLimitedSearch = true;
	d->mDelimiter = "+_-";
Erwan Croze's avatar
Erwan Croze committed
43 44 45 46 47 48
	d->mUseDelimiter = true;
	d->mCacheResult = nullptr;
}

MagicSearch::~MagicSearch() {
	resetSearchCache();
Erwan Croze's avatar
Erwan Croze committed
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 74 75 76 77 78 79 80
}

void MagicSearch::setMinWeight(const unsigned int weight) {
	L_D();
	d->mMinWeight = weight;
}

unsigned int MagicSearch::getMinWeight() const {
	L_D();
	return d->mMinWeight;
}

void MagicSearch::setMaxWeight(const unsigned int weight) {
	L_D();
	d->mMaxWeight = weight;
}

unsigned int MagicSearch::getMaxWeight() const {
	L_D();
	return d->mMaxWeight;
}

const string &MagicSearch::getDelimiter() const {
	L_D();
	return d->mDelimiter;
}

void MagicSearch::setDelimiter(const string &delimiter) {
	L_D();
	d->mDelimiter = delimiter;
}

Erwan Croze's avatar
Erwan Croze committed
81 82 83 84 85 86 87 88 89 90
bool MagicSearch::getUseDelimiter() const {
	L_D();
	return d->mUseDelimiter;
}

void MagicSearch::setUseDelimiter(bool enable) {
	L_D();
	d->mUseDelimiter = enable;
}

Erwan Croze's avatar
Erwan Croze committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
unsigned int MagicSearch::getSearchLimit() const {
	L_D();
	return d->mSearchLimit;
}

void MagicSearch::setSearchLimit(const unsigned int limit) {
	L_D();
	d->mSearchLimit = limit;
}

bool MagicSearch::getLimitedSearch() const {
	L_D();
	return d->mLimitedSearch;
}

void MagicSearch::setLimitedSearch(const bool limited) {
	L_D();
	d->mLimitedSearch = limited;
}

Erwan Croze's avatar
Erwan Croze committed
111 112 113 114 115 116 117 118 119 120
void MagicSearch::resetSearchCache() {
	L_D();
	if (d->mCacheResult) {
		delete d->mCacheResult;
		d->mCacheResult = nullptr;
	}
}

list<SearchResult> MagicSearch::getContactListFromFilter(const string &filter, const string &withDomain) {
	list<SearchResult> *resultList;
121
	list<SearchResult> returnList;
122
	LinphoneProxyConfig *proxy = nullptr;
Erwan Croze's avatar
Erwan Croze committed
123

124
	if (filter.empty()) return getFriends(withDomain);
Erwan Croze's avatar
Erwan Croze committed
125

126
	if (getSearchCache() != nullptr) {
Erwan Croze's avatar
Erwan Croze committed
127
		resultList = continueSearch(filter, withDomain);
128
		resetSearchCache();
Erwan Croze's avatar
Erwan Croze committed
129 130 131 132 133
	} else {
		resultList = beginNewSearch(filter, withDomain);
	}

	resultList->sort([](const SearchResult& lsr, const SearchResult& rsr){
134
		return (!rsr.getFriend() && lsr.getFriend()) || lsr >= rsr;
Erwan Croze's avatar
Erwan Croze committed
135 136
	});

137 138 139 140 141
	setSearchCache(resultList);
	returnList = *resultList;

	if (getLimitedSearch() && returnList.size() > getSearchLimit()) {
		auto limitIterator = returnList.begin();
142
		advance(limitIterator, (int)getSearchLimit());
Ghislain MARY's avatar
Ghislain MARY committed
143
		returnList.erase(limitIterator, returnList.end());
144 145
	}

146 147 148 149 150
	proxy = linphone_core_get_default_proxy_config(this->getCore()->getCCore());
	// Adding last item if proxy exist
	if (proxy) {
		const char *domain = linphone_proxy_config_get_domain(proxy);
		if (domain) {
Erwan Croze's avatar
Erwan Croze committed
151 152 153
			string strTmp = filter;
			transform(strTmp.begin(), strTmp.end(), strTmp.begin(), [](unsigned char c){ return tolower(c); });
			string filterAddress = "sip:" + strTmp + "@" + domain;
154
			LinphoneAddress *lastResult = linphone_core_create_address(this->getCore()->getCCore(), filterAddress.c_str());
155 156 157 158
			if (lastResult) {
				returnList.push_back(SearchResult(0, lastResult, "", nullptr));
				linphone_address_unref(lastResult);
			}
159 160
		}
	}
Erwan Croze's avatar
Erwan Croze committed
161

162
	return returnList;
Erwan Croze's avatar
Erwan Croze committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
}

/////////////////////
// Private Methods //
/////////////////////

list<SearchResult> *MagicSearch::getSearchCache() const {
	L_D();
	return d->mCacheResult;
}

void MagicSearch::setSearchCache(list<SearchResult> *cache) {
	L_D();
	if (d->mCacheResult != cache) resetSearchCache();
	d->mCacheResult = cache;
}

Erwan Croze's avatar
Erwan Croze committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
static bool findAddress(const list<SearchResult> &list, const LinphoneAddress *addr) {
	bool returnValue = false;
	char *charAddr = linphone_address_as_string_uri_only(addr);
	string sAddr = charAddr;
	for (auto r : list) {
		if (r.getAddress()) {
			char *charTmp = linphone_address_as_string_uri_only(r.getAddress());
			string tmp = charTmp;
			if (sAddr == tmp){
				returnValue = true;
				if (charTmp) bctbx_free(charTmp);
				break;
			}
			if (charTmp) bctbx_free(charTmp);
		}
	}
	if (charAddr) bctbx_free(charAddr);
	return returnValue;
}

list<SearchResult> MagicSearch::getAddressFromCallLog(const string &filter, const string &withDomain, const list<SearchResult> &currentList) {
201 202 203 204 205 206 207 208 209 210
	list<SearchResult> resultList;
	const bctbx_list_t *callLog = linphone_core_get_call_logs(this->getCore()->getCCore());

	// For all call log or when we reach the search limit
	for (const bctbx_list_t *f = callLog ; f != nullptr ; f = bctbx_list_next(f)) {
		LinphoneCallLog *log = reinterpret_cast<LinphoneCallLog*>(f->data);
		const LinphoneAddress *addr = (linphone_call_log_get_dir(log) == LinphoneCallDir::LinphoneCallIncoming) ?
		linphone_call_log_get_from_address(log) : linphone_call_log_get_to_address(log);
		if (addr) {
			if (filter.empty()) {
Erwan Croze's avatar
Erwan Croze committed
211
				if (findAddress(currentList, addr)) continue;
212 213 214 215
				resultList.push_back(SearchResult(0, addr, "", nullptr));
			} else {
				unsigned int weight = searchInAddress(addr, filter, withDomain);
				if (weight > getMinWeight()) {
Erwan Croze's avatar
Erwan Croze committed
216
					if (findAddress(currentList, addr)) continue;
217 218 219 220 221 222 223 224 225 226
					resultList.push_back(SearchResult(weight, addr, "", nullptr));
				}
			}
		}
	}

	return resultList;
}

list<SearchResult> MagicSearch::getFriends(const string &withDomain) {
227
	list<SearchResult> returnList;
228
	list<SearchResult> clResults;
229 230 231
	LinphoneFriendList *list = linphone_core_get_default_friend_list(this->getCore()->getCCore());

	for (bctbx_list_t *f = list->friends ; f != nullptr ; f = bctbx_list_next(f)) {
Erwan Croze's avatar
Erwan Croze committed
232
		LinphoneAddress *phoneAddress = nullptr;
233
		const LinphoneFriend *lFriend = reinterpret_cast<LinphoneFriend*>(f->data);
Erwan Croze's avatar
Erwan Croze committed
234
		const LinphoneAddress *lAddress = linphone_friend_get_address(lFriend);
235 236
		bctbx_list_t *lPhoneNumbers = linphone_friend_get_phone_numbers(lFriend);
		string lPhoneNumber = (lPhoneNumbers != nullptr) ? static_cast<const char*>(lPhoneNumbers->data) : "";
Erwan Croze's avatar
Erwan Croze committed
237 238 239 240 241 242 243 244 245 246
		const LinphonePresenceModel *presence = linphone_friend_get_presence_model(lFriend);
		if (lPhoneNumbers) bctbx_list_free(lPhoneNumbers);

		if (presence && !lAddress) {
			char *contact = linphone_presence_model_get_contact(presence);
			if (contact) {
				phoneAddress = linphone_core_create_address(this->getCore()->getCCore(), contact);
				bctbx_free(contact);
			}
		}
247

248
		if (!withDomain.empty()) {
Erwan Croze's avatar
Erwan Croze committed
249
			if (!lAddress && !phoneAddress)
250
				continue;
Erwan Croze's avatar
Erwan Croze committed
251 252 253
			if (withDomain != "*" &&
				withDomain != ((lAddress) ? linphone_address_get_domain(lAddress) : "") &&
				withDomain != ((phoneAddress) ? linphone_address_get_domain(phoneAddress) : ""))
254 255
				continue;
		}
Erwan Croze's avatar
Erwan Croze committed
256 257 258

		if (phoneAddress) linphone_address_unref(phoneAddress);

259
		returnList.push_back(SearchResult(1, lAddress, lPhoneNumber, lFriend));
260 261
	}

Erwan Croze's avatar
Erwan Croze committed
262
	clResults = getAddressFromCallLog("", withDomain, clResults);
263 264
	addResultsToResultsList(clResults, returnList);

265 266
	returnList.sort([](const SearchResult& lsr, const SearchResult& rsr){
		unsigned int cpt = 0;
Erwan Croze's avatar
Erwan Croze committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
		string name1 = "";
		string name2 = "";
		if (lsr.getFriend()) {
			name1 = linphone_friend_get_name(lsr.getFriend());
		} else if (lsr.getAddress()){
			name1 = linphone_address_get_display_name(lsr.getAddress()) ?
				linphone_address_get_display_name(lsr.getAddress()) : linphone_address_get_username(lsr.getAddress());
		}

		if (rsr.getFriend()) {
			name2 = linphone_friend_get_name(rsr.getFriend());
		} else if (rsr.getAddress()){
			name2 = linphone_address_get_display_name(rsr.getAddress()) ?
			linphone_address_get_display_name(rsr.getAddress()) : linphone_address_get_username(rsr.getAddress());
		}
282 283

		while (name1.size() > cpt && name2.size() > cpt) {
284 285
			int char1 = tolower(name1.at(cpt));
			int char2 = tolower(name2.at(cpt));
286 287 288 289 290 291 292 293 294 295
			if (char1 < char2) {
				return true;
			} else if (char1 > char2) {
				return false;
			}
			cpt++;
		}
		return name1.size() < name2.size();
	});

Erwan Croze's avatar
Erwan Croze committed
296
	return *uniqueItemsList(returnList);
297 298
}

Erwan Croze's avatar
Erwan Croze committed
299
list<SearchResult> *MagicSearch::beginNewSearch(const string &filter, const string &withDomain) {
300
	list<SearchResult> clResults;
Erwan Croze's avatar
Erwan Croze committed
301
	list<SearchResult> *resultList = new list<SearchResult>();
302
	LinphoneFriendList *fList = linphone_core_get_default_friend_list(this->getCore()->getCCore());
Erwan Croze's avatar
Erwan Croze committed
303 304

	// For all friends or when we reach the search limit
305
	for (bctbx_list_t *f = fList->friends ; f != nullptr ; f = bctbx_list_next(f)) {
306 307
		list<SearchResult> fResults = searchInFriend(reinterpret_cast<LinphoneFriend*>(f->data), filter, withDomain);
		addResultsToResultsList(fResults, *resultList);
Erwan Croze's avatar
Erwan Croze committed
308
	}
Erwan Croze's avatar
Erwan Croze committed
309

Erwan Croze's avatar
Erwan Croze committed
310
	clResults = getAddressFromCallLog(filter, withDomain, *resultList);
311
	addResultsToResultsList(clResults, *resultList);
312

Erwan Croze's avatar
Erwan Croze committed
313
	return uniqueItemsList(*resultList);
Erwan Croze's avatar
Erwan Croze committed
314
}
Erwan Croze's avatar
Erwan Croze committed
315

Erwan Croze's avatar
Erwan Croze committed
316 317 318
list<SearchResult> *MagicSearch::continueSearch(const string &filter, const string &withDomain) {
	list<SearchResult> *resultList = new list<SearchResult>();
	const list <SearchResult> *cacheList = getSearchCache();
Erwan Croze's avatar
Erwan Croze committed
319

Erwan Croze's avatar
Erwan Croze committed
320
	for (const auto sr : *cacheList) {
321 322
		if (sr.getAddress() || !sr.getPhoneNumber().empty()) {
			if (sr.getFriend()) {
323 324
				list<SearchResult> results = searchInFriend(sr.getFriend(), filter, withDomain);
				addResultsToResultsList(results, *resultList);
325 326 327 328 329
			} else {
				unsigned int weight = searchInAddress(sr.getAddress(), filter, withDomain);
				if (weight > getMinWeight()) {
					resultList->push_back(SearchResult(weight, sr.getAddress(), sr.getPhoneNumber(), nullptr));
				}
Erwan Croze's avatar
Erwan Croze committed
330
			}
Erwan Croze's avatar
Erwan Croze committed
331
		}
Erwan Croze's avatar
Erwan Croze committed
332
	}
Erwan Croze's avatar
Erwan Croze committed
333

Erwan Croze's avatar
Erwan Croze committed
334
	return uniqueItemsList(*resultList);
Erwan Croze's avatar
Erwan Croze committed
335
}
Erwan Croze's avatar
Erwan Croze committed
336

337 338
list<SearchResult> MagicSearch::searchInFriend(const LinphoneFriend *lFriend, const string &filter, const string &withDomain) {
	list<SearchResult> friendResult;
339
	string phoneNumber = "";
Erwan Croze's avatar
Erwan Croze committed
340
	unsigned int weight = getMinWeight();
Erwan Croze's avatar
Erwan Croze committed
341

Erwan Croze's avatar
Erwan Croze committed
342
	// NAME
343 344 345 346
	if (linphone_core_vcard_supported()) {
		if (linphone_friend_get_vcard(lFriend)) {
			weight += getWeight(linphone_vcard_get_full_name(linphone_friend_get_vcard(lFriend)), filter) * 3;
		}
Erwan Croze's avatar
Erwan Croze committed
347 348
	}

349
	//SIP URI
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
	for (const bctbx_list_t *listAddress = linphone_friend_get_addresses(lFriend);
		 listAddress != nullptr && listAddress->data != nullptr;
		 listAddress = listAddress->next) {
		const LinphoneAddress *lAddress = static_cast<LinphoneAddress*>(listAddress->data);
		if (!checkDomain(lFriend, lAddress, withDomain)) {
			if (!withDomain.empty()) {
				continue;
			}
		}

		unsigned int weightAddress = searchInAddress(lAddress, filter, withDomain) * 1;

		if ((weightAddress + weight) > getMinWeight()) {
			friendResult.push_back(SearchResult(weight + weightAddress, lAddress, phoneNumber, lFriend));
		}
	}
366

Erwan Croze's avatar
Erwan Croze committed
367
	// PHONE NUMBER
368
	LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(this->getCore()->getCCore());
Erwan Croze's avatar
Erwan Croze committed
369 370
	bctbx_list_t *begin, *phoneNumbers = linphone_friend_get_phone_numbers(lFriend);
	begin = phoneNumbers;
371
	while (phoneNumbers && phoneNumbers->data) {
372
		bool domainOk = (withDomain.empty());
Erwan Croze's avatar
Erwan Croze committed
373
		string number = static_cast<const char*>(phoneNumbers->data);
Erwan Croze's avatar
Erwan Croze committed
374
		const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lFriend, number.c_str());
375 376
		phoneNumber = number;
		if (proxy) {
377
			char * buff = linphone_proxy_config_normalize_phone_number(proxy, phoneNumber.c_str());
jehan's avatar
jehan committed
378 379 380 381
			if (buff) {
				phoneNumber = buff;
				bctbx_free(buff);
			}
382
		}
383
		unsigned int weightNumber = getWeight(phoneNumber.c_str(), filter);
384 385
		if (presence) {
			char *contact = linphone_presence_model_get_contact(presence);
386 387 388 389 390 391 392 393 394 395 396 397
			if (contact) {
				if (!domainOk) {
					LinphoneAddress *tmpAdd = linphone_core_create_address(this->getCore()->getCCore(), contact);
					if (tmpAdd) {
						string tmpDomain = linphone_address_get_domain(tmpAdd);
						domainOk = (tmpDomain == withDomain) || withDomain == "*";
						linphone_address_unref(tmpAdd);
					}
				}
				weightNumber += getWeight(contact, filter) * 2;
				bctbx_free(contact);
			}
Erwan Croze's avatar
Erwan Croze committed
398
		}
399 400 401 402 403 404 405
		if ((weightNumber + weight) > getMinWeight()) {
			if (!domainOk && linphone_friend_get_address(lFriend)) {
				string tmpDomain = linphone_address_get_domain(linphone_friend_get_address(lFriend));
				domainOk = (tmpDomain == withDomain) || withDomain == "*";
			}
			if (domainOk)
				friendResult.push_back(SearchResult(weight + weightNumber, linphone_friend_get_address(lFriend), phoneNumber, lFriend));
406
		}
Erwan Croze's avatar
Erwan Croze committed
407 408
		phoneNumbers = phoneNumbers->next;
	}
Erwan Croze's avatar
Erwan Croze committed
409
	if (begin) bctbx_list_free(begin);
Erwan Croze's avatar
Erwan Croze committed
410

411
	return friendResult;
Erwan Croze's avatar
Erwan Croze committed
412 413
}

414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
unsigned int MagicSearch::searchInAddress(const LinphoneAddress *lAddress, const string &filter, const string &withDomain) {
	unsigned int weight = getMinWeight();
	if (lAddress != nullptr && checkDomain(nullptr, lAddress, withDomain)) {
		// SIPURI
		if (linphone_address_get_username(lAddress) != nullptr) {
			weight += getWeight(linphone_address_get_username(lAddress), filter);
		}
		// DISPLAYNAME
		if (linphone_address_get_display_name(lAddress) != nullptr) {
			weight += getWeight(linphone_address_get_display_name(lAddress), filter);
		}
	}
	return weight;
}

Erwan Croze's avatar
Erwan Croze committed
429
unsigned int MagicSearch::getWeight(const string &stringWords, const string &filter) const {
430 431 432
	locale loc;
	string filterLC = filter;
	string stringWordsLC = stringWords;
Erwan Croze's avatar
Erwan Croze committed
433 434
	size_t weight = string::npos;

435 436 437 438 439
	transform(stringWordsLC.begin(), stringWordsLC.end(), stringWordsLC.begin(), [](unsigned char c){ return tolower(c); });
	transform(filterLC.begin(), filterLC.end(), filterLC.begin(), [](unsigned char c){ return tolower(c); });

	// Finding all occurrences of "filterLC" in "stringWordsLC"
	for (size_t w = stringWordsLC.find(filterLC);
Erwan Croze's avatar
Erwan Croze committed
440
		 w != string::npos;
441
	w = stringWordsLC.find(filterLC, w + filterLC.length())
442
	) {
Erwan Croze's avatar
Erwan Croze committed
443 444 445 446 447
		// weight max if occurence find at beginning
		if (w == 0) {
			weight = getMaxWeight();
		} else {
			bool isDelimiter = false;
Erwan Croze's avatar
Erwan Croze committed
448
			if (getUseDelimiter()) {
449 450
				// get the char before the matched filterLC
				const char l = stringWordsLC.at(w - 1);
Erwan Croze's avatar
Erwan Croze committed
451 452 453 454 455 456
				// Check if it's a delimiter
				for (const char d : getDelimiter()) {
					if (l == d) {
						isDelimiter = true;
						break;
					}
Erwan Croze's avatar
Erwan Croze committed
457 458 459 460 461
				}
			}
			unsigned int newWeight = getMaxWeight() - (unsigned int)((isDelimiter) ? 1 : w + 1);
			weight = (weight != string::npos) ? weight + newWeight : newWeight;
		}
462
		// Only one search on the stringWordsLC for the moment
Erwan Croze's avatar
Erwan Croze committed
463 464
		// due to weight calcul which dos not take into the case of multiple occurence
		break;
Erwan Croze's avatar
Erwan Croze committed
465 466 467 468 469
	}

	return (weight != string::npos) ? (unsigned int)(weight) : getMinWeight();
}

470
bool MagicSearch::checkDomain(const LinphoneFriend *lFriend, const LinphoneAddress *lAddress, const string &withDomain) const {
471
	bool onlyOneDomain = !withDomain.empty() && withDomain != "*";
472 473
	const LinphonePresenceModel *presenceModel = lFriend ? linphone_friend_get_presence_model(lFriend) : nullptr;
	char *contactPresence = presenceModel ? linphone_presence_model_get_contact(presenceModel) : nullptr;
474

475 476 477 478 479 480 481
	LinphoneAddress *addrPresence = nullptr;
	if (contactPresence) {
		addrPresence = linphone_core_create_address(this->getCore()->getCCore(), contactPresence);
		bctbx_free(contactPresence);
	}

	bool soFarSoGood =
482
		!onlyOneDomain || (
483
		// If we don't want Sip URI only or Address or Presence model
484
		(lAddress || presenceModel) &&
485
		// And If we don't want Sip URI only or Address match or Address presence match
486 487
		((lAddress && withDomain == linphone_address_get_domain(lAddress)) ||
			(addrPresence && withDomain == linphone_address_get_domain(addrPresence)))
488 489 490 491 492
		);

	if (addrPresence) linphone_address_unref(addrPresence);

	return soFarSoGood;
493 494
}

495 496
void MagicSearch::addResultsToResultsList(std::list<SearchResult> &results, std::list<SearchResult> &srL) {
	if (results.size() > 0) {
Erwan Croze's avatar
Erwan Croze committed
497
		srL.merge(results);
498 499 500
	}
}

Erwan Croze's avatar
Erwan Croze committed
501 502 503 504 505 506 507 508 509 510 511 512 513 514
static string getAddressFromSearchResult(const SearchResult &sr, const shared_ptr<Core> lc) {
	string sAddress = "";
	if (!sr.getAddress() && sr.getFriend()) {
		const LinphonePresenceModel *presenceModel = linphone_friend_get_presence_model(sr.getFriend());
		char *contactPresence = presenceModel ? linphone_presence_model_get_contact(presenceModel) : nullptr;

		LinphoneAddress *addrPresence = nullptr;
		if (contactPresence) {
			addrPresence = linphone_core_create_address(lc->getCCore(), contactPresence);
			if (addrPresence) {
				char *tmp = linphone_address_as_string_uri_only(addrPresence);
				sAddress = tmp;
				if (tmp) bctbx_free(tmp);
				linphone_address_unref(addrPresence);
Erwan Croze's avatar
Erwan Croze committed
515
			}
Erwan Croze's avatar
Erwan Croze committed
516
			bctbx_free(contactPresence);
Erwan Croze's avatar
Erwan Croze committed
517
		}
Erwan Croze's avatar
Erwan Croze committed
518 519 520 521 522
	} else {
		char *tmp = linphone_address_as_string_uri_only(sr.getAddress());
		sAddress = tmp;
		if (tmp) bctbx_free(tmp);
	}
Erwan Croze's avatar
Erwan Croze committed
523

Erwan Croze's avatar
Erwan Croze committed
524 525 526 527 528 529 530 531
	return sAddress;
}

list<SearchResult> *MagicSearch::uniqueItemsList(list<SearchResult> &list) {
	auto lc = this->getCore();
	list.unique([lc](const SearchResult& lsr, const SearchResult& rsr){
		string left = getAddressFromSearchResult(lsr, lc);
		string right = getAddressFromSearchResult(rsr, lc);
532

Erwan Croze's avatar
Erwan Croze committed
533 534 535 536 537
		return (!left.empty() || !right.empty()) && left == right;
	});
	return &list;
}

Erwan Croze's avatar
Erwan Croze committed
538
LINPHONE_END_NAMESPACE