chat-message.cpp 27.4 KB
Newer Older
1
/*
2
 * chat-message.cpp
Ghislain MARY's avatar
Ghislain MARY committed
3
 * Copyright (C) 2010-2017 Belledonne Communications SARL
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * 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.
9 10 11 12 13 14 15
 *
 * 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
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 */
Ronan's avatar
Ronan committed
19 20 21

#include "object/object-p.h"

22 23
#include "linphone/core.h"
#include "linphone/lpconfig.h"
Sylvain Berfini's avatar
Sylvain Berfini committed
24
#include "c-wrapper/c-wrapper.h"
25
#include "address/address.h"
26

Ronan's avatar
Ronan committed
27
#include "chat/chat-message/chat-message-p.h"
Ronan's avatar
Ronan committed
28

Ronan's avatar
Ronan committed
29 30 31 32
#include "chat/chat-room/chat-room-p.h"
#include "chat/chat-room/real-time-text-chat-room.h"
#include "chat/modifier/cpim-chat-message-modifier.h"
#include "chat/modifier/encryption-chat-message-modifier.h"
33
#include "chat/modifier/file-transfer-chat-message-modifier.h"
Ronan's avatar
Ronan committed
34
#include "chat/modifier/multipart-chat-message-modifier.h"
35
#include "content/file-content.h"
36 37
#include "content/content.h"
#include "core/core.h"
38
#include "core/core-p.h"
39
#include "logger/logger.h"
40
#include "chat/notification/imdn.h"
41

42
#include "ortp/b64.h"
43

Ronan's avatar
Ronan committed
44 45 46 47
// =============================================================================

using namespace std;

48
using namespace B64_NAMESPACE;
49

50
LINPHONE_BEGIN_NAMESPACE
51

52 53 54 55
void ChatMessagePrivate::setDirection (ChatMessage::Direction dir) {
	direction = dir;
}

56
void ChatMessagePrivate::setTime (time_t t) {
57 58 59
	time = t;
}

60
void ChatMessagePrivate::setIsReadOnly (bool readOnly) {
61 62 63
	isReadOnly = readOnly;
}

64
void ChatMessagePrivate::setState (ChatMessage::State s, bool force) {
65 66
	L_Q();

67 68 69
	if (force)
		state = s;

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	if (s == state || !q->getChatRoom())
		return;

	if (
		(state == ChatMessage::State::Displayed || state == ChatMessage::State::DeliveredToUser) &&
		(
			s == ChatMessage::State::DeliveredToUser ||
			s == ChatMessage::State::Delivered ||
			s == ChatMessage::State::NotDelivered
		)
	)
		return;

	lInfo() << "Chat message " << this << ": moving from state " <<
		linphone_chat_message_state_to_string((LinphoneChatMessageState)state) << " to " <<
		linphone_chat_message_state_to_string((LinphoneChatMessageState)s);
	state = s;

	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
	if (linphone_chat_message_get_message_state_changed_cb(msg))
		linphone_chat_message_get_message_state_changed_cb(msg)(
			msg,
			(LinphoneChatMessageState)state,
			linphone_chat_message_get_message_state_changed_cb_user_data(msg)
		);

	LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
	if (cbs && linphone_chat_message_cbs_get_msg_state_changed(cbs))
		linphone_chat_message_cbs_get_msg_state_changed(cbs)(msg, linphone_chat_message_get_state(msg));
99 100

	store();
101 102
}

103
belle_http_request_t *ChatMessagePrivate::getHttpRequest () const {
104
	return fileTransferChatMessageModifier.getHttpRequest();
105 106
}

107
void ChatMessagePrivate::setHttpRequest (belle_http_request_t *request) {
108
	fileTransferChatMessageModifier.setHttpRequest(request);
109 110
}

111
SalOp *ChatMessagePrivate::getSalOp () const {
112 113 114
	return salOp;
}

115
void ChatMessagePrivate::setSalOp (SalOp *op) {
116 117 118
	salOp = op;
}

119
SalCustomHeader *ChatMessagePrivate::getSalCustomHeaders () const {
120 121 122
	return salCustomHeaders;
}

123
void ChatMessagePrivate::setSalCustomHeaders (SalCustomHeader *headers) {
124 125 126
	salCustomHeaders = headers;
}

127
void ChatMessagePrivate::addSalCustomHeader (const string &name, const string &value) {
128 129 130
	salCustomHeaders = sal_custom_header_append(salCustomHeaders, name.c_str(), value.c_str());
}

131
void ChatMessagePrivate::removeSalCustomHeader (const string &name) {
132 133 134
	salCustomHeaders = sal_custom_header_remove(salCustomHeaders, name.c_str());
}

135
string ChatMessagePrivate::getSalCustomHeaderValue (const string &name) {
136
	return L_C_TO_STRING(sal_custom_header_find(salCustomHeaders, name.c_str()));
137 138
}

139 140
// -----------------------------------------------------------------------------
// Below methods are only for C API backward compatibility...
141 142
// -----------------------------------------------------------------------------

Sylvain Berfini's avatar
Sylvain Berfini committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
bool ChatMessagePrivate::hasTextContent() const {
	for (const Content *c : contents) {
		if (c->getContentType() == ContentType::PlainText) {
			return true;
		}
	}
	return false;
}

const Content* ChatMessagePrivate::getTextContent() const {
	for (const Content *c : contents) {
		if (c->getContentType() == ContentType::PlainText) {
			return c;
		}
	}
158
	return &Utils::getEmptyConstRefObject<Content>();
Sylvain Berfini's avatar
Sylvain Berfini committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
}

bool ChatMessagePrivate::hasFileTransferContent() const {
	for (const Content *c : contents) {
		if (c->getContentType() == ContentType::FileTransfer) {
			return true;
		}
	}
	return false;
}

const Content* ChatMessagePrivate::getFileTransferContent() const {
	for (const Content *c : contents) {
		if (c->getContentType() == ContentType::FileTransfer) {
			return c;
		}
	}
176
	return &Utils::getEmptyConstRefObject<Content>();
Sylvain Berfini's avatar
Sylvain Berfini committed
177 178 179 180 181 182 183 184 185 186 187
}

const string &ChatMessagePrivate::getFileTransferFilepath () const {
	return fileTransferFilePath;
}

void ChatMessagePrivate::setFileTransferFilepath (const string &path) {
	fileTransferFilePath = path;
}

const string &ChatMessagePrivate::getAppdata () const {
188 189 190
	for (const Content *c : contents) {
		if (c->getContentType().isFile()) {
			FileContent *fileContent = (FileContent *)c;
191
			return fileContent->getAppData("app");
192 193 194
		}
	}
	return Utils::getEmptyConstRefObject<string>();
Sylvain Berfini's avatar
Sylvain Berfini committed
195 196 197
}

void ChatMessagePrivate::setAppdata (const string &data) {
198 199 200
	for (const Content *c : contents) {
		if (c->getContentType().isFile()) {
			FileContent *fileContent = (FileContent *)c;
201 202
			fileContent->setAppData("app", data);
			break;
203 204
		}
	}
Sylvain Berfini's avatar
Sylvain Berfini committed
205 206 207 208 209 210 211 212 213 214
}

const string &ChatMessagePrivate::getExternalBodyUrl () const {
	if (hasFileTransferContent()) {
		FileTransferContent *content = (FileTransferContent*) getFileTransferContent();
		return content->getFileUrl();
	}
	return Utils::getEmptyConstRefObject<string>();
}

215
const ContentType &ChatMessagePrivate::getContentType () {
216
	if (direction == ChatMessage::Direction::Incoming) {
217
		if (contents.size() > 0) {
218 219
			Content *content = contents.front();
			cContentType = content->getContentType();
220 221 222 223 224 225 226 227
		} else {
			cContentType = internalContent.getContentType();
		}
	} else {
		if (internalContent.getContentType().isValid()) {
			cContentType = internalContent.getContentType();
		} else {
			if (contents.size() > 0) {
228 229
				Content *content = contents.front();
				cContentType = content->getContentType();
230
			}
231
		}
232
	}
233 234 235
	return cContentType;
}

236
void ChatMessagePrivate::setContentType (const ContentType &contentType) {
237
	internalContent.setContentType(contentType);
238 239
}

240
const string &ChatMessagePrivate::getText () {
241
	if (direction == ChatMessage::Direction::Incoming) {
Sylvain Berfini's avatar
Sylvain Berfini committed
242 243
		if (hasTextContent()) {
			cText = getTextContent()->getBodyAsString();
244 245 246
		} else if (contents.size() > 0) {
			Content *content = contents.front();
			cText = content->getBodyAsString();
247 248 249 250
		} else {
			cText = internalContent.getBodyAsString();
		}
	} else {
Sylvain Berfini's avatar
Sylvain Berfini committed
251 252
		if (hasTextContent()) {
			cText = getTextContent()->getBodyAsString();
253
		} else if (!internalContent.isEmpty()) {
254 255 256
			cText = internalContent.getBodyAsString();
		} else {
			if (contents.size() > 0) {
257 258
				Content *content = contents.front();
				cText = content->getBodyAsString();
259
			}
260
		}
261
	}
262 263 264
	return cText;
}

265
void ChatMessagePrivate::setText (const string &text) {
266
	internalContent.setBody(text);
267 268
}

269
LinphoneContent *ChatMessagePrivate::getFileTransferInformation () const {
Sylvain Berfini's avatar
Sylvain Berfini committed
270 271
	if (hasFileTransferContent()) {
		return getFileTransferContent()->toLinphoneContent();
272
	}
273 274 275 276 277 278
	for (const Content *c : contents) {
		if (c->getContentType().isFile()) {
			FileContent *fileContent = (FileContent *)c;
			return fileContent->toLinphoneContent();
		}
	}
279
	return NULL;
280 281
}

282 283 284
void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_content) {
	L_Q();

285
	// Create a FileContent, it will create the FileTransferContent at upload time
286
	FileContent *fileContent = new FileContent();
287
	ContentType contentType(linphone_content_get_type(c_content), linphone_content_get_subtype(c_content));
288 289 290
	fileContent->setContentType(contentType);
	fileContent->setFileSize(linphone_content_get_size(c_content));
	fileContent->setFileName(linphone_content_get_name(c_content));
291
	if (linphone_content_get_string_buffer(c_content) != NULL) {
292
		fileContent->setBody(linphone_content_get_string_buffer(c_content));
293 294
	}

295
	q->addContent(*fileContent);
296 297
}

298
bool ChatMessagePrivate::downloadFile () {
299 300
	L_Q();

301 302 303
	for (auto &content : contents)
		if (content->getContentType() == ContentType::FileTransfer)
			return q->downloadFile(*static_cast<FileTransferContent *>(content));
304

305
	return false;
306 307 308 309
}

// -----------------------------------------------------------------------------

310
void ChatMessagePrivate::sendImdn (Imdn::Type imdnType, LinphoneReason reason) {
Sylvain Berfini's avatar
Sylvain Berfini committed
311 312 313
	L_Q();

	shared_ptr<ChatMessage> msg = q->getChatRoom()->createMessage();
314

Sylvain Berfini's avatar
Sylvain Berfini committed
315 316
	Content *content = new Content();
	content->setContentType("message/imdn+xml");
317
	content->setBody(Imdn::createXml(id, time, imdnType, reason));
Sylvain Berfini's avatar
Sylvain Berfini committed
318 319
	msg->addContent(*content);

320
	msg->getPrivate()->send();
321 322 323
}

LinphoneReason ChatMessagePrivate::receive () {
324
	L_Q();
325
	int errorCode = 0;
326
	LinphoneReason reason = LinphoneReasonNone;
327

328 329 330
	shared_ptr<Core> core = q->getCore();
	shared_ptr<ChatRoom> chatRoom = q->getChatRoom();

331 332
	setState(ChatMessage::State::Delivered);

333 334 335 336
	// ---------------------------------------
	// Start of message modification
	// ---------------------------------------

337 338 339 340 341 342 343 344
	if ((currentRecvStep &ChatMessagePrivate::Step::Cpim) == ChatMessagePrivate::Step::Cpim) {
		lInfo() << "Cpim step already done, skipping";
	} else {
		if (internalContent.getContentType() == ContentType::Cpim) {
			CpimChatMessageModifier ccmm;
			ccmm.decode(q->getSharedFromThis(), errorCode);
		}
		currentRecvStep |= ChatMessagePrivate::Step::Cpim;
345 346
	}

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
	if ((currentRecvStep &ChatMessagePrivate::Step::Encryption) == ChatMessagePrivate::Step::Encryption) {
		lInfo() << "Encryption step already done, skipping";
	} else {
		EncryptionChatMessageModifier ecmm;
		ChatMessageModifier::Result result = ecmm.decode(q->getSharedFromThis(), errorCode);
		if (result == ChatMessageModifier::Result::Error) {
			/* Unable to decrypt message */
			if (chatRoom)
				chatRoom->getPrivate()->notifyUndecryptableMessageReceived(q->getSharedFromThis());
			reason = linphone_error_code_to_reason(errorCode);
			q->sendDeliveryNotification(reason);
			return reason;
		} else if (result == ChatMessageModifier::Result::Suspended) {
			currentRecvStep |= ChatMessagePrivate::Step::Encryption;
			return LinphoneReasonNone;
		}
		currentRecvStep |= ChatMessagePrivate::Step::Encryption;
364
	}
365

366 367 368 369 370 371 372
	if ((currentRecvStep &ChatMessagePrivate::Step::Multipart) == ChatMessagePrivate::Step::Multipart) {
		lInfo() << "Multipart step already done, skipping";
	} else {
		MultipartChatMessageModifier mcmm;
		mcmm.decode(q->getSharedFromThis(), errorCode);
		currentRecvStep |= ChatMessagePrivate::Step::Multipart;
	}
373

374
	if ((currentRecvStep & ChatMessagePrivate::Step::FileUpload) == ChatMessagePrivate::Step::FileUpload) {
Sylvain Berfini's avatar
Sylvain Berfini committed
375
		lInfo() << "File download step already done, skipping";
376 377 378 379 380
	} else {
		// This will check if internal content is FileTransfer and make the appropriate changes
		fileTransferChatMessageModifier.decode(q->getSharedFromThis(), errorCode);
		currentRecvStep |= ChatMessagePrivate::Step::FileUpload;
	}
381

382 383
	if (contents.size() == 0) {
		// All previous modifiers only altered the internal content, let's fill the content list
384
		contents.push_back(&internalContent);
385 386
	}

387 388 389 390
	// ---------------------------------------
	// End of message modification
	// ---------------------------------------

391 392
	if (errorCode <= 0) {
		bool foundSupportContentType = false;
393 394
		for (Content *c : contents) {
			if (linphone_core_is_content_type_supported(core->getCCore(), c->getContentType().asString().c_str())) {
395 396 397
				foundSupportContentType = true;
				break;
			} else
398
			lError() << "Unsupported content-type: " << c->getContentType().asString();
399
		}
400 401 402 403 404

		if (!foundSupportContentType) {
			errorCode = 415;
			lError() << "No content-type in the contents list is supported...";
		}
405 406
	}

407
	// Check if this is in fact an outgoing message (case where this is a message sent by us from an other device).
408
	Address me(linphone_core_get_identity(core->getCCore()));
409
	if (me.weakEqual(q->getFromAddress()))
410
		setDirection(ChatMessage::Direction::Outgoing);
411

412
	// Check if this is a duplicate message.
413
	if (chatRoom && chatRoom->findMessageWithDirection(q->getImdnMessageId(), direction))
414
		return core->getCCore()->chat_deny_code;
415

416 417
	if (errorCode > 0) {
		reason = linphone_error_code_to_reason(errorCode);
418
		q->sendDeliveryNotification(reason);
419
		return reason;
420 421
	}

422
	store();
423 424 425 426

	return reason;
}

427
void ChatMessagePrivate::send () {
428 429
	L_Q();
	SalOp *op = salOp;
430
	LinphoneCall *call = nullptr;
431
	int errorCode = 0;
432

433
	if ((currentSendStep & ChatMessagePrivate::Step::FileUpload) == ChatMessagePrivate::Step::FileUpload) {
434 435
		lInfo() << "File upload step already done, skipping";
	} else {
436
		ChatMessageModifier::Result result = fileTransferChatMessageModifier.encode(q->getSharedFromThis(), errorCode);
437
		if (result == ChatMessageModifier::Result::Error) {
438
			setState(ChatMessage::State::NotDelivered);
439 440 441 442 443
			return;
		} else if (result == ChatMessageModifier::Result::Suspended) {
			setState(ChatMessage::State::InProgress);
			return;
		}
444
		currentSendStep |= ChatMessagePrivate::Step::FileUpload;
445 446
	}

447
	shared_ptr<Core> core = q->getCore();
448
	if (lp_config_get_int(core->getCCore()->config, "sip", "chat_use_call_dialogs", 0) != 0) {
449
		call = linphone_core_get_call_by_remote_address(core->getCCore(), q->getToAddress().asString().c_str());
450 451
		if (call) {
			if (linphone_call_get_state(call) == LinphoneCallConnected || linphone_call_get_state(call) == LinphoneCallStreamsRunning ||
452 453
					linphone_call_get_state(call) == LinphoneCallPaused || linphone_call_get_state(call) == LinphoneCallPausing ||
					linphone_call_get_state(call) == LinphoneCallPausedByRemote) {
454
				lInfo() << "send SIP msg through the existing call.";
455
				op = linphone_call_get_op(call);
456
				string identity = linphone_core_find_best_identity(core->getCCore(), linphone_call_get_remote_address(call));
457
				if (identity.empty()) {
458
					LinphoneAddress *addr = linphone_address_new(q->getToAddress().asString().c_str());
459
					LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(core->getCCore(), addr);
460 461 462
					if (proxy) {
						identity = L_GET_CPP_PTR_FROM_C_OBJECT(linphone_proxy_config_get_identity_address(proxy))->asString();
					} else {
463
						identity = linphone_core_get_primary_contact(core->getCCore());
464 465 466 467 468 469 470
					}
					linphone_address_unref(addr);
				}
			}
		}
	}

471
	if (!op) {
472
		LinphoneAddress *peer = linphone_address_new(q->getToAddress().asString().c_str());
473
		/* Sending out of call */
474
		salOp = op = new SalMessageOp(core->getCCore()->sal);
475
		linphone_configure_op(
476 477
			core->getCCore(), op, peer, getSalCustomHeaders(),
			!!lp_config_get_int(core->getCCore()->config, "sip", "chat_msg_with_contact", 0)
478
		);
479
		op->set_user_pointer(L_GET_C_BACK_PTR(q));     /* If out of call, directly store msg */
480 481
		linphone_address_unref(peer);
	}
482 483
	op->set_from(q->getFromAddress().asString().c_str());
	op->set_to(q->getToAddress().asString().c_str());
484

485 486 487 488
	// ---------------------------------------
	// Start of message modification
	// ---------------------------------------

489
	if (applyModifiers) {
490
		if ((currentSendStep &ChatMessagePrivate::Step::Multipart) == ChatMessagePrivate::Step::Multipart) {
491 492 493 494 495 496 497
			lInfo() << "Multipart step already done, skipping";
		} else {
			if (contents.size() > 1) {
				MultipartChatMessageModifier mcmm;
				mcmm.encode(q->getSharedFromThis(), errorCode);
			}
			currentSendStep |= ChatMessagePrivate::Step::Multipart;
498
		}
499

500
		if ((currentSendStep &ChatMessagePrivate::Step::Encryption) == ChatMessagePrivate::Step::Encryption) {
501 502 503 504 505 506
			lInfo() << "Encryption step already done, skipping";
		} else {
			EncryptionChatMessageModifier ecmm;
			ChatMessageModifier::Result result = ecmm.encode(q->getSharedFromThis(), errorCode);
			if (result == ChatMessageModifier::Result::Error) {
				sal_error_info_set((SalErrorInfo *)op->get_error_info(), SalReasonNotAcceptable, "SIP", errorCode, "Unable to encrypt IM", nullptr);
507
				setState(ChatMessage::State::NotDelivered);
508 509 510 511 512
				return;
			} else if (result == ChatMessageModifier::Result::Suspended) {
				currentSendStep |= ChatMessagePrivate::Step::Encryption;
				return;
			}
513
			currentSendStep |= ChatMessagePrivate::Step::Encryption;
514
		}
515

516
		if ((currentSendStep &ChatMessagePrivate::Step::Cpim) == ChatMessagePrivate::Step::Cpim) {
517 518
			lInfo() << "Cpim step already done, skipping";
		} else {
519 520
			int defaultValue = !!lp_config_get_string(core->getCCore()->config, "misc", "conference_factory_uri", nullptr);
			if (lp_config_get_int(core->getCCore()->config, "sip", "use_cpim", defaultValue) == 1) {
521 522 523 524
				CpimChatMessageModifier ccmm;
				ccmm.encode(q->getSharedFromThis(), errorCode);
			}
			currentSendStep |= ChatMessagePrivate::Step::Cpim;
525
		}
526
	}
527

528 529 530 531
	// ---------------------------------------
	// End of message modification
	// ---------------------------------------

532
	if (internalContent.isEmpty())
533
		internalContent = *(contents.front());
534

Sylvain Berfini's avatar
Sylvain Berfini committed
535 536
	auto msgOp = dynamic_cast<SalMessageOpInterface *>(op);
	if (internalContent.getContentType().isValid()) {
537 538 539
		msgOp->send_message(internalContent.getContentType().asString().c_str(), internalContent.getBodyAsString().c_str());
	} else
		msgOp->send_message(ContentType::PlainText.asString().c_str(), internalContent.getBodyAsString().c_str());
540

Benjamin REIS's avatar
Benjamin REIS committed
541 542 543 544
	// Restore FileContents and remove FileTransferContents
	list<Content*>::iterator i = contents.begin();
	while (i != contents.end()) {
		Content *content = *i;
545 546
		if (content->getContentType() == ContentType::FileTransfer) {
			FileTransferContent *fileTransferContent = (FileTransferContent *)content;
Benjamin REIS's avatar
Benjamin REIS committed
547
			contents.erase(i++);
548 549
			q->addContent(*fileTransferContent->getFileContent());
			delete fileTransferContent;
Benjamin REIS's avatar
Benjamin REIS committed
550 551
		} else {
			++i;
552
		}
553
	}
554

555 556
	if (q->getImdnMessageId().empty())
		q->setImdnMessageId(op->get_call_id());   /* must be known at that time */
557

558
	//store(); // Store will be done right below in the setState(InProgress)
559

560 561 562 563 564
	if (call && linphone_call_get_op(call) == op) {
		/* In this case, chat delivery status is not notified, so unrefing chat message right now */
		/* Might be better fixed by delivering status, but too costly for now */
		return;
	}
565

566
	/* If operation failed, we should not change message state */
567
	if (direction == ChatMessage::Direction::Outgoing) {
568 569 570 571 572
		setIsReadOnly(true);
		setState(ChatMessage::State::InProgress);
	}
}

573 574
void ChatMessagePrivate::store() {
	L_Q();
575

576 577 578
	// TODO: store message in the future
	if (linphone_core_conference_server_enabled(q->getCore()->getCCore())) return;

579 580 581 582 583 584 585 586 587 588 589
	bool messageToBeStored = false;
	for (Content *c : contents) {
		ContentType contentType = c->getContentType();
		if (contentType == ContentType::FileTransfer || contentType == ContentType::PlainText || contentType.isFile()) {
			messageToBeStored = true;
		}
	}
	if (!messageToBeStored) {
		return;
	}

590 591 592 593
	unique_ptr<MainDb> &mainDb = q->getChatRoom()->getCore()->getPrivate()->mainDb;
	if (dbKey.isValid()) {
		shared_ptr<EventLog> eventLog = mainDb->getEventFromKey(dbKey);
		mainDb->updateEvent(eventLog);
594 595 596 597 598 599 600 601 602 603 604 605

		if (direction == ChatMessage::Direction::Incoming) {
			if (!hasFileTransferContent()) {
				// Incoming message doesn't have any download waiting anymore, we can remove it's event from the transients
				q->getChatRoom()->getPrivate()->removeTransientEvent(eventLog);
			}
		} else {
			if (state == ChatMessage::State::Delivered || state == ChatMessage::State::NotDelivered) {
				// Once message has reached this state it won't change anymore so we can remove the event from the transients
				q->getChatRoom()->getPrivate()->removeTransientEvent(eventLog);
			}
		}
606
	} else {
607 608
		shared_ptr<EventLog> eventLog = make_shared<ConferenceChatMessageEvent>(time, q->getSharedFromThis());
		mainDb->addEvent(eventLog);
609 610 611 612 613 614 615 616 617 618

		if (direction == ChatMessage::Direction::Incoming) {
			if (hasFileTransferContent()) {
				// Keep the event in the transient list, message storage can be updated in near future
				q->getChatRoom()->getPrivate()->addTransientEvent(eventLog);
			}
		} else {
			// Keep event in transient to be able to store in database state changes
			q->getChatRoom()->getPrivate()->addTransientEvent(eventLog);
		}
619
	}
620 621
}

622 623
// -----------------------------------------------------------------------------

624
ChatMessage::ChatMessage (const shared_ptr<ChatRoom> &chatRoom, ChatMessage::Direction direction) :
625 626 627 628 629 630
	Object(*new ChatMessagePrivate), CoreAccessor(chatRoom->getCore()) {
	L_ASSERT(chatRoom);
	L_D();

	d->chatRoom = chatRoom;
	d->chatRoomId = chatRoom->getChatRoomId();
631
	if (direction == Direction::Outgoing) {
632 633
		d->fromAddress = d->chatRoomId.getLocalAddress();
		d->toAddress = d->chatRoomId.getPeerAddress();
634
	} else {
635 636
		d->fromAddress = d->chatRoomId.getPeerAddress();
		d->toAddress = d->chatRoomId.getLocalAddress();
637 638
	}
	d->direction = direction;
639 640 641 642 643 644 645 646 647
}

ChatMessage::~ChatMessage () {
	L_D();
	for (Content *content : d->contents)
		delete content;

	if (d->salOp)
		d->salOp->release();
648
}
649 650 651

shared_ptr<ChatRoom> ChatMessage::getChatRoom () const {
	L_D();
652 653

	shared_ptr<ChatRoom> chatRoom = d->chatRoom.lock();
654
	if (!chatRoom)
655
		chatRoom = getCore()->getOrCreateBasicChatRoom(d->chatRoomId);
656 657

	return chatRoom;
658 659 660 661 662 663 664 665 666 667 668 669 670 671
}

// -----------------------------------------------------------------------------

time_t ChatMessage::getTime () const {
	L_D();
	return d->time;
}

bool ChatMessage::isSecured () const {
	L_D();
	return d->isSecured;
}

672
void ChatMessage::setIsSecured (bool isSecured) {
673 674 675 676 677 678 679 680 681
	L_D();
	d->isSecured = isSecured;
}

ChatMessage::Direction ChatMessage::getDirection () const {
	L_D();
	return d->direction;
}

682
ChatMessage::State ChatMessage::getState () const {
683 684 685 686
	L_D();
	return d->state;
}

687
const string &ChatMessage::getImdnMessageId () const {
688 689 690 691
	L_D();
	return d->id;
}

692
void ChatMessage::setImdnMessageId (const string &id) {
693 694 695 696
	L_D();
	d->id = id;
}

697
bool ChatMessage::isRead () const {
698
	L_D();
699

700
	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(getCore()->getCCore());
701 702 703 704 705 706 707 708 709
	if (linphone_im_notif_policy_get_recv_imdn_displayed(policy) && d->state == State::Displayed)
		return true;

	if (
		linphone_im_notif_policy_get_recv_imdn_delivered(policy) &&
		(d->state == State::DeliveredToUser || d->state == State::Displayed)
	)
		return true;

710
	return d->state == State::Delivered || d->state == State::Displayed || d->state == State::DeliveredToUser;
711 712
}

713
const IdentityAddress &ChatMessage::getFromAddress () const {
714
	L_D();
715
	return d->fromAddress;
716 717
}

718
const IdentityAddress &ChatMessage::getToAddress () const {
719
	L_D();
720
	return d->toAddress;
721 722 723 724
}

// -----------------------------------------------------------------------------

725
const LinphoneErrorInfo *ChatMessage::getErrorInfo () const {
726
	L_D();
727
	if (!d->errorInfo) d->errorInfo = linphone_error_info_new();   // let's do it mutable
728 729 730 731 732 733 734 735 736
	linphone_error_info_from_sal_op(d->errorInfo, d->salOp);
	return d->errorInfo;
}

bool ChatMessage::isReadOnly () const {
	L_D();
	return d->isReadOnly;
}

737
const list<Content *> &ChatMessage::getContents () const {
738
	L_D();
739
	return d->contents;
740 741
}

742
void ChatMessage::addContent (Content &content) {
743 744 745
	L_D();
	if (d->isReadOnly) return;

746
	d->contents.push_back(&content);
747 748
}

749
void ChatMessage::removeContent (const Content &content) {
750 751 752
	L_D();
	if (d->isReadOnly) return;

753
	d->contents.remove(&const_cast<Content &>(content));
754 755
}

756
const Content &ChatMessage::getInternalContent () const {
Sylvain Berfini's avatar
Sylvain Berfini committed
757 758 759 760
	L_D();
	return d->internalContent;
}

761
void ChatMessage::setInternalContent (const Content &content) {
762 763 764 765
	L_D();
	d->internalContent = content;
}

766 767 768 769 770 771 772
string ChatMessage::getCustomHeaderValue (const string &headerName) const {
	L_D();
	try {
		return d->customHeaders.at(headerName);
	} catch (const exception &) {
		// Key doesn't exist.
	}
773
	return nullptr;
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
}

void ChatMessage::addCustomHeader (const string &headerName, const string &headerValue) {
	L_D();
	if (d->isReadOnly) return;

	d->customHeaders[headerName] = headerValue;
}

void ChatMessage::removeCustomHeader (const string &headerName) {
	L_D();
	if (d->isReadOnly) return;

	d->customHeaders.erase(headerName);
}

790
void ChatMessage::updateState (State state) {
791 792 793 794 795
	L_D();

	d->setState(state);
}

796
void ChatMessage::send () {
797 798
	L_D();

799
	// Do not allow sending a message that is already being sent or that has been correctly delivered/displayed
800 801
	if ((d->state == State::InProgress) || (d->state == State::Delivered) || (d->state == State::FileTransferDone) ||
			(d->state == State::DeliveredToUser) || (d->state == State::Displayed)) {
802
		lWarning() << "Cannot send chat message in state " << linphone_chat_message_state_to_string((LinphoneChatMessageState)d->state);
803 804 805
		return;
	}

806 807 808
	shared_ptr<ChatRoom> chatRoom = getChatRoom();
	if (chatRoom)
		chatRoom->getPrivate()->sendMessage(getSharedFromThis());
809 810
}

811
void ChatMessage::sendDeliveryNotification (LinphoneReason reason) {
812
	L_D();
813

814
	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(getCore()->getCCore());
815
	if (linphone_im_notif_policy_get_send_imdn_delivered(policy))
816
		d->sendImdn(Imdn::Type::Delivery, reason);
817 818
}

819
void ChatMessage::sendDisplayNotification () {
820
	L_D();
821

822
	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(getCore()->getCCore());
823
	if (linphone_im_notif_policy_get_send_imdn_displayed(policy))
824
		d->sendImdn(Imdn::Type::Display, LinphoneReasonNone);
825
}
826

827
bool ChatMessage::downloadFile(FileTransferContent &fileTransferContent) {
828
	L_D();
829
	return d->fileTransferChatMessageModifier.downloadFile(getSharedFromThis(), &fileTransferContent);
830 831
}

832
void ChatMessage::cancelFileTransfer () {
833
	L_D();
834
	if (d->fileTransferChatMessageModifier.isFileTransferInProgressAndValid()) {
835 836
		if (d->state == State::InProgress) {
			d->setState(State::NotDelivered);
837
		}
838
		d->fileTransferChatMessageModifier.cancelFileTransfer();
839