main-db.cpp 46 KB
Newer Older
Ronan's avatar
Ronan committed
1
/*
2
 * main-db.cpp
Ghislain MARY's avatar
Ghislain MARY committed
3
 * Copyright (C) 2010-2017 Belledonne Communications SARL
Ronan's avatar
Ronan committed
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.
Ronan's avatar
Ronan committed
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.
Ronan's avatar
Ronan committed
18 19
 */

20
#include <algorithm>
21
#include <ctime>
22

Ronan's avatar
Ronan committed
23 24 25 26
#ifdef SOCI_ENABLED
	#include <soci/soci.h>
#endif // ifdef SOCI_ENABLED

27 28
#include "linphone/utils/utils.h"

Ronan's avatar
Ronan committed
29
#include "chat/chat-message/chat-message-p.h"
30
#include "chat/chat-room/chat-room.h"
31
#include "conference/participant.h"
32
#include "content/content-type.h"
Ronan's avatar
Ronan committed
33
#include "content/content.h"
Ronan's avatar
Ronan committed
34
#include "core/core-p.h"
35
#include "db/session/db-session-provider.h"
Ronan's avatar
Ronan committed
36
#include "event-log/event-log-p.h"
37
#include "event-log/events.h"
Ronan's avatar
Ronan committed
38
#include "logger/logger.h"
Ronan's avatar
Ronan committed
39
#include "main-db-p.h"
Ronan's avatar
Ronan committed
40 41 42

// =============================================================================

Ronan's avatar
Ronan committed
43 44
using namespace std;

Ronan's avatar
Ronan committed
45 46 47 48
LINPHONE_BEGIN_NAMESPACE

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

Ronan's avatar
Ronan committed
49
MainDb::MainDb (const shared_ptr<Core> &core) : CoreAccessor(core), AbstractDb(*new MainDbPrivate) {}
Ronan's avatar
Ronan committed
50

51 52
#ifdef SOCI_ENABLED

Ronan's avatar
Ronan committed
53
// -----------------------------------------------------------------------------
54
// Soci backend.
Ronan's avatar
Ronan committed
55 56
// -----------------------------------------------------------------------------

57 58 59 60 61
	template<typename T>
	struct EnumToSql {
		T first;
		const char *second;
	};
62

63 64 65 66 67 68 69
	template<typename T>
	static constexpr const char *mapEnumToSql (const EnumToSql<T> enumToSql[], size_t n, T key) {
		return n == 0 ? "" : (
			enumToSql[n - 1].first == key ? enumToSql[n - 1].second : mapEnumToSql(enumToSql, n - 1, key)
		);
	}

70
	static constexpr EnumToSql<MainDb::Filter> eventFilterToSql[] = {
71
		{ MainDb::ConferenceCallFilter, "3, 4" },
Ronan's avatar
Ronan committed
72
		{ MainDb::ConferenceChatMessageFilter, "5" },
73
		{ MainDb::ConferenceInfoFilter, "1, 2, 6, 7, 8, 9, 10, 11, 12" }
74
	};
75

76
	static constexpr const char *mapEventFilterToSql (MainDb::Filter filter) {
77 78 79
		return mapEnumToSql(
			eventFilterToSql, sizeof eventFilterToSql / sizeof eventFilterToSql[0], filter
		);
80 81
	}

Ronan's avatar
Ronan committed
82 83
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
84 85 86 87 88
	static string buildSqlEventFilter (
		const list<MainDb::Filter> &filters,
		MainDb::FilterMask mask,
		const string &condKeyWord = "WHERE"
	) {
89
		L_ASSERT(
90 91
			find_if(filters.cbegin(), filters.cend(), [](const MainDb::Filter &filter) {
					return filter == MainDb::NoFilter;
92 93 94
				}) == filters.cend()
		);

95
		if (mask == MainDb::NoFilter)
96 97 98 99 100 101 102 103 104 105
			return "";

		bool isStart = true;
		string sql;
		for (const auto &filter : filters) {
			if (!(mask & filter))
				continue;

			if (isStart) {
				isStart = false;
Ronan's avatar
Ronan committed
106
				sql += " " + condKeyWord + " type IN (";
107
			} else
Ronan's avatar
Ronan committed
108
				sql += ", ";
109 110 111
			sql += mapEventFilterToSql(filter);
		}

Ronan's avatar
Ronan committed
112 113 114
		if (!isStart)
			sql += ") ";

115 116 117
		return sql;
	}

118 119
// -----------------------------------------------------------------------------

120
	long long MainDbPrivate::insertSipAddress (const string &sipAddress) {
121 122 123
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

124
		long long id;
125 126
		*session << "SELECT id FROM sip_address WHERE value = :sipAddress", soci::use(sipAddress), soci::into(id);
		if (session->got_data())
127 128
			return id;

Ronan's avatar
Ronan committed
129
		lInfo() << "Insert new sip address in database: `" << sipAddress << "`.";
130 131
		*session << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress);
		return q->getLastInsertId();
132 133
	}

134
	void MainDbPrivate::insertContent (long long eventId, const Content &content) {
135 136
		L_Q();

137 138
		soci::session *session = dbSession.getBackendSession<soci::session>();

139
		long long contentTypeId = insertContentType(content.getContentType().asString());
140
		*session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES"
Ronan's avatar
Ronan committed
141
			"  (:eventId, :contentTypeId, :body)", soci::use(eventId), soci::use(contentTypeId),
142
			soci::use(content.getBodyAsString());
143

144
		long long messageContentId = q->getLastInsertId();
145
		for (const auto &appData : content.getAppDataMap())
146
			*session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, key, data) VALUES"
147 148
				"  (:messageContentId, :key, :data)",
				soci::use(messageContentId), soci::use(appData.first), soci::use(appData.second);
149 150
	}

151
	long long MainDbPrivate::insertContentType (const string &contentType) {
152 153 154
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

155
		long long id;
156 157
		*session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(id);
		if (session->got_data())
158 159
			return id;

Ronan's avatar
Ronan committed
160
		lInfo() << "Insert new content type in database: `" << contentType << "`.";
161 162
		*session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType);
		return q->getLastInsertId();
163 164
	}

165
	long long MainDbPrivate::insertChatRoom (long long sipAddressId, int capabilities, const tm &date) {
166 167
		soci::session *session = dbSession.getBackendSession<soci::session>();

168
		long long id;
169
		*session << "SELECT peer_sip_address_id FROM chat_room WHERE peer_sip_address_id = :sipAddressId",
170
			soci::use(sipAddressId), soci::into(id);
Ronan's avatar
Ronan committed
171 172
		if (!session->got_data()) {
			lInfo() << "Insert new chat room in database: `" << sipAddressId << "` (capabilities=" << capabilities << ").";
173 174 175
			*session << "INSERT INTO chat_room (peer_sip_address_id, creation_date, last_update_date, capabilities, subject) VALUES"
				"  (:sipAddressId, :creationDate, :lastUpdateDate, :capabilities, '')",
				soci::use(sipAddressId), soci::use(date), soci::use(date), soci::use(capabilities);
Ronan's avatar
Ronan committed
176
		}
177
		else
178
			*session << "UPDATE chat_room SET last_update_date = :lastUpdateDate WHERE peer_sip_address_id = :sipAddressId",
179
				soci::use(date), soci::use(sipAddressId);
180

181 182 183
		return sipAddressId;
	}

184
	void MainDbPrivate::insertChatRoomParticipant (long long chatRoomId, long long sipAddressId, bool isAdmin) {
185
		soci::session *session = dbSession.getBackendSession<soci::session>();
186 187 188 189 190 191
		soci::statement statement = (
			session->prepare << "UPDATE chat_room_participant SET is_admin = :isAdmin"
				"  WHERE chat_room_id = :chatRoomId AND sip_address_id = :sipAddressId",
				soci::use(static_cast<int>(isAdmin)), soci::use(chatRoomId), soci::use(sipAddressId)
		);
		statement.execute(true);
Ronan's avatar
Ronan committed
192 193
		if (statement.get_affected_rows() == 0) {
			lInfo() << "Insert new chat room participant in database: `" << sipAddressId << "` (isAdmin=" << isAdmin << ").";
194 195 196
			*session << "INSERT INTO chat_room_participant (chat_room_id, sip_address_id, is_admin)"
				"  VALUES (:chatRoomId, :sipAddressId, :isAdmin)",
				soci::use(chatRoomId), soci::use(sipAddressId), soci::use(static_cast<int>(isAdmin));
Ronan's avatar
Ronan committed
197
		}
198 199
	}

200
	void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) {
201 202
		soci::session *session = dbSession.getBackendSession<soci::session>();
		soci::statement statement = (
203
			session->prepare << "UPDATE chat_message_participant SET state = :state"
Ronan's avatar
Ronan committed
204 205
				"  WHERE event_id = :eventId AND sip_address_id = :sipAddressId",
				soci::use(state), soci::use(eventId), soci::use(sipAddressId)
206 207
		);
		statement.execute(true);
208
		if (statement.get_affected_rows() == 0 && state != static_cast<int>(ChatMessage::State::Displayed))
209
			*session << "INSERT INTO chat_message_participant (event_id, sip_address_id, state)"
Ronan's avatar
Ronan committed
210 211
				"  VALUES (:eventId, :sipAddressId, :state)",
				soci::use(eventId), soci::use(sipAddressId), soci::use(state);
212 213
	}

Ronan's avatar
Ronan committed
214 215
// -----------------------------------------------------------------------------

216
	shared_ptr<EventLog> MainDbPrivate::selectGenericConferenceEvent (
217
		long long eventId,
218 219 220 221
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
Ronan's avatar
Ronan committed
222 223 224 225 226 227
		switch (type) {
			case EventLog::Type::None:
				return nullptr;

			case EventLog::Type::ConferenceCreated:
			case EventLog::Type::ConferenceDestroyed:
228
				return selectConferenceEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
229

230 231
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
232
				return selectConferenceCallEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
233 234

			case EventLog::Type::ConferenceChatMessage:
235
				return selectConferenceChatMessageEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
236 237 238 239 240

			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
241
				return selectConferenceParticipantEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
242 243 244

			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
245
				return selectConferenceParticipantDeviceEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
246 247

			case EventLog::Type::ConferenceSubjectChanged:
248
				return selectConferenceSubjectEvent(eventId, type, date, peerAddress);
Ronan's avatar
Ronan committed
249 250 251 252 253
		}

		return nullptr;
	}

254
	shared_ptr<EventLog> MainDbPrivate::selectConferenceEvent (
255
		long long eventId,
256 257 258 259
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
260 261 262 263 264 265 266 267 268
		// Useless here.
		(void)eventId;

		// TODO: Use cache.
		return make_shared<ConferenceEvent>(
			type,
			date,
			Address(peerAddress)
		);
Ronan's avatar
Ronan committed
269 270
	}

271
	shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
272
		long long eventId,
273 274 275 276
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
Ronan's avatar
Ronan committed
277 278 279 280
		// TODO.
		return nullptr;
	}

281
	shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
282
		long long eventId,
283 284 285 286
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
Ronan's avatar
Ronan committed
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
		L_Q();

		shared_ptr<Core> core = q->getCore();

		// TODO: Avoid address creation.
		shared_ptr<ChatRoom> chatRoom = core->findChatRoom(Address(peerAddress));
		if (!chatRoom)
			return nullptr;

		string localSipAddress;
		string remoteSipAddress;
		string imdnMessageId;
		int state;
		int direction;
		int isSecured;

303
		soci::session *session = dbSession.getBackendSession<soci::session>();
Ronan's avatar
Ronan committed
304
		*session << "SELECT local_sip_address.value, remote_sip_address.value, imdn_message_id, state, direction, is_secured"
305 306 307 308 309
			"  FROM event, conference_chat_message_event, sip_address AS local_sip_address,"
			"  sip_address AS remote_sip_address"
			"  WHERE event_id = event.id"
			"  AND local_sip_address_id = local_sip_address.id"
			"  AND remote_sip_address_id = remote_sip_address.id"
Ronan's avatar
Ronan committed
310 311 312
			"  AND remote_sip_address.value = :peerAddress", soci::into(localSipAddress), soci::into(remoteSipAddress),
			soci::into(imdnMessageId), soci::into(state), soci::into(direction), soci::into(isSecured),
			soci::use(peerAddress);
313 314

		// TODO: Create me.
Ronan's avatar
Ronan committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328
		// TODO: Use cache, do not fetch the same message twice.
		shared_ptr<ChatMessage> chatMessage = make_shared<ChatMessage>(chatRoom);

		chatMessage->getPrivate()->setState(static_cast<ChatMessage::State>(state));
		chatMessage->getPrivate()->setDirection(static_cast<ChatMessage::Direction>(direction));
		chatMessage->setIsSecured(static_cast<bool>(isSecured));

		if (direction == static_cast<int>(ChatMessage::Direction::Outgoing)) {
			chatMessage->setFromAddress(Address(localSipAddress));
			chatMessage->setToAddress(Address(remoteSipAddress));
		} else {
			chatMessage->setFromAddress(Address(remoteSipAddress));
			chatMessage->setToAddress(Address(localSipAddress));
		}
329 330 331 332 333 334

		// TODO: Use cache.
		return make_shared<ConferenceChatMessageEvent>(
			date,
			chatMessage
		);
Ronan's avatar
Ronan committed
335 336
	}

337
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
338
		long long eventId,
339 340 341 342
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
343 344 345 346
		unsigned int notifyId;
		string participantAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
347 348
		*session << "SELECT notify_id, participant_address.value"
			"  FROM conference_notified_event, conference_participant_event, sip_address as participant_address"
349
			"  WHERE conference_participant_event.event_id = :eventId"
Ronan's avatar
Ronan committed
350 351
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
			"    AND participant_address.id = participant_address_id",
352 353 354 355 356 357
			soci::into(notifyId), soci::into(participantAddress), soci::use(eventId);

		// TODO: Use cache.
		return make_shared<ConferenceParticipantEvent>(
			type,
			date,
358
			false,
359 360 361 362
			Address(peerAddress),
			notifyId,
			Address(participantAddress)
		);
Ronan's avatar
Ronan committed
363 364
	}

365
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantDeviceEvent (
366
		long long eventId,
367 368 369 370
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
371 372 373 374 375
		unsigned int notifyId;
		string participantAddress;
		string gruuAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
376 377 378
		*session << "SELECT notify_id, participant_address.value, gruu_address.value"
			"  FROM conference_notified_event, conference_participant_event, conference_participant_device_event,"
			"    sip_address AS participant_address, sip_address AS gruu_address"
379 380
			"  WHERE conference_participant_device_event.event_id = :eventId"
			"    AND conference_participant_event.event_id = conference_participant_device_event.event_id"
381 382 383
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
			"    AND participant_address.id = participant_address_id"
			"    AND gruu_address.id = gruu_address_id",
384 385 386 387 388 389
			soci::into(notifyId), soci::into(participantAddress), soci::into(gruuAddress), soci::use(eventId);

		// TODO: Use cache.
		return make_shared<ConferenceParticipantDeviceEvent>(
			type,
			date,
390
			false,
391 392 393 394 395
			Address(peerAddress),
			notifyId,
			Address(participantAddress),
			Address(gruuAddress)
		);
Ronan's avatar
Ronan committed
396 397
	}

398
	shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
399
		long long eventId,
400 401 402 403 404 405 406
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
		unsigned int notifyId;
		string subject;

407 408 409 410 411 412
		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "SELECT notify_id, subject"
			"  FROM conference_notified_event, conference_subject_event"
			"  WHERE conference_subject_event.event_id = :eventId"
			"    AND conference_notified_event.event_id = conference_subject_event.event_id",
			soci::into(notifyId), soci::into(subject), soci::use(eventId);
413 414 415 416

		// TODO: Use cache.
		return make_shared<ConferenceSubjectEvent>(
			date,
417
			false,
418 419 420 421
			Address(peerAddress),
			notifyId,
			subject
		);
Ronan's avatar
Ronan committed
422 423
	}

424 425
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
426
	long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
427 428 429 430
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

		*session << "INSERT INTO event (type, date) VALUES (:type, :date)",
431
		soci::use(static_cast<int>(eventLog->getType())), soci::use(Utils::getTimeTAsTm(eventLog->getTime()));
432
		return q->getLastInsertId();
433 434
	}

Ronan's avatar
Ronan committed
435
	long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
436 437
		long long eventId = insertEvent(eventLog);
		long long curChatRoomId = insertSipAddress(
Ronan's avatar
Ronan committed
438
			static_pointer_cast<ConferenceEvent>(eventLog)->getConferenceAddress().asString()
439 440 441 442 443 444 445 446 447 448 449 450
		);

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_event (event_id, chat_room_id)"
			"  VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(curChatRoomId);

		if (chatRoomId)
			*chatRoomId = curChatRoomId;

		return eventId;
	}

Ronan's avatar
Ronan committed
451
	long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
452 453 454 455
		// TODO.
		return 0;
	}

Ronan's avatar
Ronan committed
456 457
	long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
458 459 460 461 462 463
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return -1;
		}

464
		tm eventTime = Utils::getTimeTAsTm(eventLog->getTime());
465

466 467
		long long localSipAddressId = insertSipAddress(chatMessage->getLocalAddress().asString());
		long long remoteSipAddressId = insertSipAddress(chatMessage->getRemoteAddress().asString());
468
		insertChatRoom(remoteSipAddressId, chatRoom->getCapabilities(), eventTime);
469
		long long eventId = insertConferenceEvent(eventLog);
470

471
		soci::session *session = dbSession.getBackendSession<soci::session>();
Ronan's avatar
Ronan committed
472

473 474 475 476 477 478 479 480 481 482 483 484
		*session << "INSERT INTO conference_chat_message_event ("
			"  event_id, local_sip_address_id, remote_sip_address_id,"
			"  state, direction, imdn_message_id, is_secured"
			") VALUES ("
			"  :eventId, :localSipaddressId, :remoteSipaddressId,"
			"  :state, :direction, :imdnMessageId, :isSecured"
			")", soci::use(eventId), soci::use(localSipAddressId), soci::use(remoteSipAddressId),
			soci::use(static_cast<int>(chatMessage->getState())), soci::use(static_cast<int>(chatMessage->getDirection())),
			soci::use(chatMessage->getImdnMessageId()), soci::use(chatMessage->isSecured() ? 1 : 0);

		for (const auto &content : chatMessage->getContents())
			insertContent(eventId, content);
485

486 487 488
		return eventId;
	}

Ronan's avatar
Ronan committed
489
	long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog) {
490 491
		long long chatRoomId;
		long long eventId = insertConferenceEvent(eventLog, &chatRoomId);
Ronan's avatar
Ronan committed
492
		unsigned int lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
493 494 495

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
Ronan's avatar
Ronan committed
496 497 498
			"  VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId);
		*session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE peer_sip_address_id = :chatRoomId",
			soci::use(lastNotifyId), soci::use(chatRoomId);
499

500 501 502
		return eventId;
	}

Ronan's avatar
Ronan committed
503
	long long MainDbPrivate::insertConferenceParticipantEvent (const shared_ptr<EventLog> &eventLog) {
504 505
		long long eventId = insertConferenceNotifiedEvent(eventLog);
		long long participantAddressId = insertSipAddress(
Ronan's avatar
Ronan committed
506
			static_pointer_cast<ConferenceParticipantEvent>(eventLog)->getParticipantAddress().asString()
507 508
		);

509
		soci::session *session = dbSession.getBackendSession<soci::session>();
510
		*session << "INSERT INTO conference_participant_event (event_id, participant_address_id)"
511
			"  VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
512

513 514 515
		return eventId;
	}

Ronan's avatar
Ronan committed
516
	long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
517 518
		long long eventId = insertConferenceParticipantEvent(eventLog);
		long long gruuAddressId = insertSipAddress(
Ronan's avatar
Ronan committed
519
			static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog)->getGruuAddress().asString()
520 521 522
		);

		soci::session *session = dbSession.getBackendSession<soci::session>();
523
		*session << "INSERT INTO conference_participant_device_event (event_id, gruu_address_id)"
524
			"  VALUES (:eventId, :gruuAddressId)", soci::use(eventId), soci::use(gruuAddressId);
525

526
		return eventId;
527 528
	}

Ronan's avatar
Ronan committed
529
	long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
530
		long long eventId = insertConferenceNotifiedEvent(eventLog);
531 532

		soci::session *session = dbSession.getBackendSession<soci::session>();
533
		*session << "INSERT INTO conference_subject_event (event_id, subject)"
534
			"  VALUES (:eventId, :subject)", soci::use(eventId), soci::use(
Ronan's avatar
Ronan committed
535
				static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject()
536
			);
537

538
		return eventId;
539 540
	}

541
// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
542

543
	void MainDb::init () {
544
		L_D();
Ronan's avatar
Ronan committed
545
		soci::session *session = d->dbSession.getBackendSession<soci::session>();
546

Ronan's avatar
Ronan committed
547
		*session <<
548
			"CREATE TABLE IF NOT EXISTS sip_address ("
549
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","
Ronan's avatar
Ronan committed
550
			"  value VARCHAR(255) UNIQUE NOT NULL"
551 552
			")";

553
		*session <<
554
			"CREATE TABLE IF NOT EXISTS content_type ("
555
			"  id" + primaryKeyStr("UNSIGNED SMALLINT") + ","
Ronan's avatar
Ronan committed
556
			"  value VARCHAR(255) UNIQUE NOT NULL"
557 558
			")";

Ronan's avatar
Ronan committed
559
		*session <<
560
			"CREATE TABLE IF NOT EXISTS event ("
561
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","
562
			"  type TINYINT UNSIGNED NOT NULL,"
563
			"  date DATE NOT NULL"
564 565
			")";

Ronan's avatar
Ronan committed
566
		*session <<
567
			"CREATE TABLE IF NOT EXISTS chat_room ("
568
			// Server (for conference) or user sip address.
569
			"  peer_sip_address_id" + primaryKeyStr("UNSIGNED BIGINT") + ","
570 571

			// Dialog creation date.
572 573 574 575
			"  creation_date DATE NOT NULL,"

			// Last event date (call, message...).
			"  last_update_date DATE NOT NULL,"
576

577 578 579
			// ConferenceChatRoom, BasicChatRoom, RTT...
			"capabilities TINYINT UNSIGNED,"

580 581 582
			// Chatroom subject.
			"  subject VARCHAR(255),"

Ronan's avatar
Ronan committed
583
			"  last_notify_id INT UNSIGNED,"
584

585
			"  FOREIGN KEY (peer_sip_address_id)"
586 587 588 589
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

590 591
		*session <<
			"CREATE TABLE IF NOT EXISTS chat_room_participant ("
592 593 594
			"  chat_room_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

595 596
			"  is_admin BOOLEAN NOT NULL,"

597
			"  PRIMARY KEY (chat_room_id, sip_address_id),"
598 599 600 601 602 603
			"  FOREIGN KEY (chat_room_id)"
			"    REFERENCES chat_room(peer_sip_address_id)"
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (sip_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
604 605 606 607
			")";

		*session <<
			"CREATE TABLE IF NOT EXISTS conference_event ("
608 609 610
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  chat_room_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
611 612 613 614 615 616 617 618 619

			"  FOREIGN KEY (event_id)"
			"    REFERENCES event(id)"
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (chat_room_id)"
			"    REFERENCES chat_room(peer_sip_address_id)"
			"    ON DELETE CASCADE"
			")";

620 621
		*session <<
			"CREATE TABLE IF NOT EXISTS conference_notified_event ("
622 623
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

624 625 626 627 628 629 630
			"  notify_id INT UNSIGNED NOT NULL,"

			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_event(event_id)"
			"    ON DELETE CASCADE"
			")";

631 632
		*session <<
			"CREATE TABLE IF NOT EXISTS conference_participant_event ("
633 634 635
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  participant_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
636

637 638
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_notified_event(event_id)"
639 640 641 642 643 644 645 646
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (participant_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

		*session <<
			"CREATE TABLE IF NOT EXISTS conference_participant_device_event ("
647 648 649
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  gruu_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
650

651 652
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_participant_event(event_id)"
653 654 655 656 657 658 659 660
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (gruu_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

		*session <<
			"CREATE TABLE IF NOT EXISTS conference_subject_event ("
661 662
			"  event_id" + primaryKeyStr("BIGINT") + ","

663
			"  subject VARCHAR(255) NOT NULL,"
664

665 666
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_notified_event(event_id)"
667
			"    ON DELETE CASCADE"
668 669
			")";

Ronan's avatar
Ronan committed
670
		*session <<
671
			"CREATE TABLE IF NOT EXISTS conference_chat_message_event ("
672 673 674 675
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  local_sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  remote_sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
676 677 678 679

			// See: https://tools.ietf.org/html/rfc5438#section-6.3
			"  imdn_message_id VARCHAR(255) NOT NULL,"

680 681
			"  state TINYINT UNSIGNED NOT NULL,"
			"  direction TINYINT UNSIGNED NOT NULL,"
682
			"  is_secured BOOLEAN NOT NULL,"
683

684
			"  FOREIGN KEY (event_id)"
685
			"    REFERENCES conference_event(event_id)"
686
			"    ON DELETE CASCADE,"
687 688
			"  FOREIGN KEY (local_sip_address_id)"
			"    REFERENCES sip_address(id)"
689
			"    ON DELETE CASCADE,"
690
			"  FOREIGN KEY (remote_sip_address_id)"
691
			"    REFERENCES sip_address(id)"
692 693 694
			"    ON DELETE CASCADE"
			")";

695
		*session <<
696
			"CREATE TABLE IF NOT EXISTS chat_message_participant ("
697 698
			"  event_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
699 700
			"  state TINYINT UNSIGNED NOT NULL,"

Ronan's avatar
Ronan committed
701 702
			"  PRIMARY KEY (event_id, sip_address_id),"
			"  FOREIGN KEY (event_id)"
703
			"    REFERENCES conference_chat_message_event(event_id)"
704 705 706
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (sip_address_id)"
			"    REFERENCES sip_address(id)"
707
			"    ON DELETE CASCADE"
708 709
			")";

710
		*session <<
711
			"CREATE TABLE IF NOT EXISTS chat_message_content ("
712 713 714 715
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  event_id " + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  content_type_id" + primaryKeyRefStr("UNSIGNED SMALLINT") + ","
716
			"  body TEXT NOT NULL,"
717

Ronan's avatar
Ronan committed
718
			"  FOREIGN KEY (event_id)"
719
			"    REFERENCES conference_chat_message_event(event_id)"
720 721 722
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (content_type_id)"
			"    REFERENCES content_type(id)"
723 724
			"    ON DELETE CASCADE"
			")";
725

726
		*session <<
727
			"CREATE TABLE IF NOT EXISTS chat_message_content_app_data ("
728 729
			"  chat_message_content_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

730 731 732
			"  key VARCHAR(255),"
			"  data BLOB,"

733 734 735
			"  PRIMARY KEY (chat_message_content_id, key),"
			"  FOREIGN KEY (chat_message_content_id)"
			"    REFERENCES chat_message_content(id)"
736 737 738
			"    ON DELETE CASCADE"
			")";

739
		*session <<
740
			"CREATE TABLE IF NOT EXISTS conference_message_crypto_data ("
741 742
			"  event_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

743
			"  key VARCHAR(255),"
744 745
			"  data BLOB,"

Ronan's avatar
Ronan committed
746 747
			"  PRIMARY KEY (event_id, key),"
			"  FOREIGN KEY (event_id)"
748
			"    REFERENCES conference_chat_message_event(event_id)"
749
			"    ON DELETE CASCADE"
750
			")";
751 752 753 754

		// Trigger to delete participant_message cache entries.
		string displayedId = Utils::toString(static_cast<int>(ChatMessage::State::Displayed));
		string participantMessageDeleter =
755 756
			"CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter"
			"  AFTER UPDATE OF state ON chat_message_participant FOR EACH ROW"
757 758
			"  WHEN NEW.state = ";
		participantMessageDeleter += displayedId;
759
		participantMessageDeleter += " AND (SELECT COUNT(*) FROM ("
760 761
			"    SELECT state FROM chat_message_participant WHERE"
			"    NEW.event_id = chat_message_participant.event_id"
762 763 764 765 766
			"    AND state <> ";
		participantMessageDeleter += displayedId;
		participantMessageDeleter += "    LIMIT 1"
			"  )) = 0"
			"  BEGIN"
767 768
			"  DELETE FROM chat_message_participant WHERE NEW.event_id = chat_message_participant.event_id;"
			"  UPDATE conference_chat_message_event SET state = ";
769
		participantMessageDeleter += displayedId;
Ronan's avatar
Ronan committed
770
		participantMessageDeleter += " WHERE event_id = NEW.event_id;"
771 772 773
			"  END";

		*session << participantMessageDeleter;
Ronan's avatar
Ronan committed
774
	}
775

Ronan's avatar
Ronan committed
776
	bool MainDb::addEvent (const shared_ptr<EventLog> &eventLog) {
777 778
		L_D();

Ronan's avatar
Ronan committed
779 780
		if (!isConnected()) {
			lWarning() << "Unable to add event. Not connected.";
781
			return false;
Ronan's avatar
Ronan committed
782 783
		}

784 785 786 787 788 789
		bool soFarSoGood = false;

		L_BEGIN_LOG_EXCEPTION

		soci::transaction tr(*d->dbSession.getBackendSession<soci::session>());

Ronan's avatar
Ronan committed
790
		switch (eventLog->getType()) {
791
			case EventLog::Type::None:
Ronan's avatar
Ronan committed
792
				return false;
793

794 795 796
			case EventLog::Type::ConferenceCreated:
			case EventLog::Type::ConferenceDestroyed:
				d->insertConferenceEvent(eventLog);
797 798
				break;

799 800
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
801
				d->insertConferenceCallEvent(eventLog);
802
				break;
803

804 805
			case EventLog::Type::ConferenceChatMessage:
				d->insertConferenceChatMessageEvent(eventLog);
806 807
				break;

808 809 810 811
			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
812 813 814
				d->insertConferenceParticipantEvent(eventLog);
				break;

815 816
			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
817 818 819
				d->insertConferenceParticipantDeviceEvent(eventLog);
				break;

820
			case EventLog::Type::ConferenceSubjectChanged:
821
				d->insertConferenceSubjectEvent(eventLog);
Ronan's avatar
Ronan committed
822 823 824
				break;
		}

825 826 827 828 829 830 831
		tr.commit();

		soFarSoGood = true;

		L_END_LOG_EXCEPTION

		return soFarSoGood;
Ronan's avatar
Ronan committed
832 833
	}

Ronan's avatar
Ronan committed
834
	bool MainDb::deleteEvent (const shared_ptr<EventLog> &eventLog) {
Ronan's avatar
Ronan committed
835 836
		L_D();

Ronan's avatar
Ronan committed
837 838 839 840 841
		if (!isConnected()) {
			lWarning() << "Unable to delete event. Not connected.";
			return false;
		}

Ronan's avatar
Ronan committed
842
		long long &storageId = eventLog->getPrivate()->storageId;
Ronan's avatar
Ronan committed
843
		if (storageId < 0)