main-db.cpp 44.8 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"

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

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

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

Ronan's avatar
Ronan committed
44 45 46 47
LINPHONE_BEGIN_NAMESPACE

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

48 49 50 51
MainDb::MainDb (Core *core) : AbstractDb(*new MainDbPrivate) {
	L_D();
	d->core = core;
}
Ronan's avatar
Ronan committed
52

53 54
#ifdef SOCI_ENABLED

Ronan's avatar
Ronan committed
55
// -----------------------------------------------------------------------------
56
// Soci backend.
Ronan's avatar
Ronan committed
57 58
// -----------------------------------------------------------------------------

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

65 66 67 68 69 70 71
	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)
		);
	}

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

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

Ronan's avatar
Ronan committed
84 85
// -----------------------------------------------------------------------------

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

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

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

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

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

117 118 119
		return sql;
	}

120 121
// -----------------------------------------------------------------------------

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

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

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

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

139 140
		soci::session *session = dbSession.getBackendSession<soci::session>();

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

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

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

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

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

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

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

183 184 185
		return sipAddressId;
	}

186
	void MainDbPrivate::insertChatRoomParticipant (long long chatRoomId, long long sipAddressId, bool isAdmin) {
187
		soci::session *session = dbSession.getBackendSession<soci::session>();
188 189 190 191 192 193
		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
194 195
		if (statement.get_affected_rows() == 0) {
			lInfo() << "Insert new chat room participant in database: `" << sipAddressId << "` (isAdmin=" << isAdmin << ").";
196 197 198
			*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
199
		}
200 201
	}

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

Ronan's avatar
Ronan committed
216 217
// -----------------------------------------------------------------------------

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

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

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

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

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

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

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

		return nullptr;
	}

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

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

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

283
	shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
284
		long long eventId,
285 286 287 288
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "SELECT event_id, type, date, local_sip_address.value, "
			"remote_sip_address.value, imdn_message_id, state, direction, is_secured"
			"  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"
			"  AND remote_sip_address.value = :peerAddress", soci::use(peerAddress);

		// TODO: Create me.
		shared_ptr<ChatMessage> chatMessage;

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

309
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
310
		long long eventId,
311 312 313 314
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
315 316 317 318
		unsigned int notifyId;
		string participantAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
319 320
		*session << "SELECT notify_id, participant_address.value"
			"  FROM conference_notified_event, conference_participant_event, sip_address as participant_address"
321
			"  WHERE conference_participant_event.event_id = :eventId"
Ronan's avatar
Ronan committed
322 323
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
			"    AND participant_address.id = participant_address_id",
324 325 326 327 328 329 330 331 332 333
			soci::into(notifyId), soci::into(participantAddress), soci::use(eventId);

		// TODO: Use cache.
		return make_shared<ConferenceParticipantEvent>(
			type,
			date,
			Address(peerAddress),
			notifyId,
			Address(participantAddress)
		);
Ronan's avatar
Ronan committed
334 335
	}

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

		soci::session *session = dbSession.getBackendSession<soci::session>();
347 348 349
		*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"
350 351
			"  WHERE conference_participant_device_event.event_id = :eventId"
			"    AND conference_participant_event.event_id = conference_participant_device_event.event_id"
352 353 354
			"    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",
355 356 357 358 359 360 361 362 363 364 365
			soci::into(notifyId), soci::into(participantAddress), soci::into(gruuAddress), soci::use(eventId);

		// TODO: Use cache.
		return make_shared<ConferenceParticipantDeviceEvent>(
			type,
			date,
			Address(peerAddress),
			notifyId,
			Address(participantAddress),
			Address(gruuAddress)
		);
Ronan's avatar
Ronan committed
366 367
	}

368
	shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
369
		long long eventId,
370 371 372 373 374 375 376
		EventLog::Type type,
		time_t date,
		const string &peerAddress
	) const {
		unsigned int notifyId;
		string subject;

377 378 379 380 381 382
		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);
383 384 385 386 387 388 389 390

		// TODO: Use cache.
		return make_shared<ConferenceSubjectEvent>(
			date,
			Address(peerAddress),
			notifyId,
			subject
		);
Ronan's avatar
Ronan committed
391 392
	}

393 394
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
395
	long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
396 397 398 399
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

		*session << "INSERT INTO event (type, date) VALUES (:type, :date)",
400
		soci::use(static_cast<int>(eventLog->getType())), soci::use(Utils::getTimeTAsTm(eventLog->getTime()));
401
		return q->getLastInsertId();
402 403
	}

Ronan's avatar
Ronan committed
404
	long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
405 406
		long long eventId = insertEvent(eventLog);
		long long curChatRoomId = insertSipAddress(
Ronan's avatar
Ronan committed
407
			static_pointer_cast<ConferenceEvent>(eventLog)->getConferenceAddress().asString()
408 409 410 411 412 413 414 415 416 417 418 419
		);

		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
420
	long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
421 422 423 424
		// TODO.
		return 0;
	}

Ronan's avatar
Ronan committed
425 426
	long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
427 428 429 430 431 432
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return -1;
		}

433
		tm eventTime = Utils::getTimeTAsTm(eventLog->getTime());
434

435 436
		long long localSipAddressId = insertSipAddress(chatMessage->getLocalAddress().asString());
		long long remoteSipAddressId = insertSipAddress(chatMessage->getRemoteAddress().asString());
437
		insertChatRoom(remoteSipAddressId, chatRoom->getCapabilities(), eventTime);
438
		long long eventId = insertConferenceEvent(eventLog);
439

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

442 443 444 445 446 447 448 449 450 451 452 453
		*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);
454

455 456 457
		return eventId;
	}

Ronan's avatar
Ronan committed
458
	long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog) {
459 460
		long long chatRoomId;
		long long eventId = insertConferenceEvent(eventLog, &chatRoomId);
Ronan's avatar
Ronan committed
461
		unsigned int lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
462 463 464

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
Ronan's avatar
Ronan committed
465 466 467
			"  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);
468

469 470 471
		return eventId;
	}

Ronan's avatar
Ronan committed
472
	long long MainDbPrivate::insertConferenceParticipantEvent (const shared_ptr<EventLog> &eventLog) {
473 474
		long long eventId = insertConferenceNotifiedEvent(eventLog);
		long long participantAddressId = insertSipAddress(
Ronan's avatar
Ronan committed
475
			static_pointer_cast<ConferenceParticipantEvent>(eventLog)->getParticipantAddress().asString()
476 477
		);

478
		soci::session *session = dbSession.getBackendSession<soci::session>();
479
		*session << "INSERT INTO conference_participant_event (event_id, participant_address_id)"
480
			"  VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
481

482 483 484
		return eventId;
	}

Ronan's avatar
Ronan committed
485
	long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
486 487
		long long eventId = insertConferenceParticipantEvent(eventLog);
		long long gruuAddressId = insertSipAddress(
Ronan's avatar
Ronan committed
488
			static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog)->getGruuAddress().asString()
489 490 491
		);

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

495
		return eventId;
496 497
	}

Ronan's avatar
Ronan committed
498
	long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
499
		long long eventId = insertConferenceNotifiedEvent(eventLog);
500 501

		soci::session *session = dbSession.getBackendSession<soci::session>();
502
		*session << "INSERT INTO conference_subject_event (event_id, subject)"
503
			"  VALUES (:eventId, :subject)", soci::use(eventId), soci::use(
Ronan's avatar
Ronan committed
504
				static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject()
505
			);
506

507
		return eventId;
508 509
	}

510
// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
511

512
	void MainDb::init () {
513
		L_D();
Ronan's avatar
Ronan committed
514
		soci::session *session = d->dbSession.getBackendSession<soci::session>();
515

Ronan's avatar
Ronan committed
516
		*session <<
517
			"CREATE TABLE IF NOT EXISTS sip_address ("
518
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","
Ronan's avatar
Ronan committed
519
			"  value VARCHAR(255) UNIQUE NOT NULL"
520 521
			")";

522
		*session <<
523
			"CREATE TABLE IF NOT EXISTS content_type ("
524
			"  id" + primaryKeyStr("UNSIGNED SMALLINT") + ","
Ronan's avatar
Ronan committed
525
			"  value VARCHAR(255) UNIQUE NOT NULL"
526 527
			")";

Ronan's avatar
Ronan committed
528
		*session <<
529
			"CREATE TABLE IF NOT EXISTS event ("
530
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","
531
			"  type TINYINT UNSIGNED NOT NULL,"
532
			"  date DATE NOT NULL"
533 534
			")";

Ronan's avatar
Ronan committed
535
		*session <<
536
			"CREATE TABLE IF NOT EXISTS chat_room ("
537
			// Server (for conference) or user sip address.
538
			"  peer_sip_address_id" + primaryKeyStr("UNSIGNED BIGINT") + ","
539 540

			// Dialog creation date.
541 542 543 544
			"  creation_date DATE NOT NULL,"

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

546 547 548
			// ConferenceChatRoom, BasicChatRoom, RTT...
			"capabilities TINYINT UNSIGNED,"

549 550 551
			// Chatroom subject.
			"  subject VARCHAR(255),"

Ronan's avatar
Ronan committed
552
			"  last_notify_id INT UNSIGNED,"
553

554
			"  FOREIGN KEY (peer_sip_address_id)"
555 556 557 558
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

559 560
		*session <<
			"CREATE TABLE IF NOT EXISTS chat_room_participant ("
561 562 563
			"  chat_room_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

564 565
			"  is_admin BOOLEAN NOT NULL,"

566
			"  PRIMARY KEY (chat_room_id, sip_address_id),"
567 568 569 570 571 572
			"  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"
573 574 575 576
			")";

		*session <<
			"CREATE TABLE IF NOT EXISTS conference_event ("
577 578 579
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  chat_room_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
580 581 582 583 584 585 586 587 588

			"  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"
			")";

589 590
		*session <<
			"CREATE TABLE IF NOT EXISTS conference_notified_event ("
591 592
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

593 594 595 596 597 598 599
			"  notify_id INT UNSIGNED NOT NULL,"

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

600 601
		*session <<
			"CREATE TABLE IF NOT EXISTS conference_participant_event ("
602 603 604
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

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

606 607
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_notified_event(event_id)"
608 609 610 611 612 613 614 615
			"    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 ("
616 617 618
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

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

620 621
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_participant_event(event_id)"
622 623 624 625 626 627 628 629
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (gruu_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

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

632
			"  subject VARCHAR(255) NOT NULL,"
633

634 635
			"  FOREIGN KEY (event_id)"
			"    REFERENCES conference_notified_event(event_id)"
636
			"    ON DELETE CASCADE"
637 638
			")";

Ronan's avatar
Ronan committed
639
		*session <<
640
			"CREATE TABLE IF NOT EXISTS conference_chat_message_event ("
641 642 643 644
			"  event_id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  local_sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  remote_sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
645 646 647 648

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

649 650
			"  state TINYINT UNSIGNED NOT NULL,"
			"  direction TINYINT UNSIGNED NOT NULL,"
651
			"  is_secured BOOLEAN NOT NULL,"
652

653
			"  FOREIGN KEY (event_id)"
654
			"    REFERENCES conference_event(event_id)"
655
			"    ON DELETE CASCADE,"
656 657
			"  FOREIGN KEY (local_sip_address_id)"
			"    REFERENCES sip_address(id)"
658
			"    ON DELETE CASCADE,"
659
			"  FOREIGN KEY (remote_sip_address_id)"
660
			"    REFERENCES sip_address(id)"
661 662 663
			"    ON DELETE CASCADE"
			")";

664
		*session <<
665
			"CREATE TABLE IF NOT EXISTS chat_message_participant ("
666 667
			"  event_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  sip_address_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","
668 669
			"  state TINYINT UNSIGNED NOT NULL,"

Ronan's avatar
Ronan committed
670 671
			"  PRIMARY KEY (event_id, sip_address_id),"
			"  FOREIGN KEY (event_id)"
672
			"    REFERENCES conference_chat_message_event(event_id)"
673 674 675
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (sip_address_id)"
			"    REFERENCES sip_address(id)"
676
			"    ON DELETE CASCADE"
677 678
			")";

679
		*session <<
680
			"CREATE TABLE IF NOT EXISTS chat_message_content ("
681 682 683 684
			"  id" + primaryKeyStr("UNSIGNED BIGINT") + ","

			"  event_id " + primaryKeyRefStr("UNSIGNED BIGINT") + ","
			"  content_type_id" + primaryKeyRefStr("UNSIGNED SMALLINT") + ","
685
			"  body TEXT NOT NULL,"
686

Ronan's avatar
Ronan committed
687
			"  FOREIGN KEY (event_id)"
688
			"    REFERENCES conference_chat_message_event(event_id)"
689 690 691
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (content_type_id)"
			"    REFERENCES content_type(id)"
692 693
			"    ON DELETE CASCADE"
			")";
694

695
		*session <<
696
			"CREATE TABLE IF NOT EXISTS chat_message_content_app_data ("
697 698
			"  chat_message_content_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

699 700 701
			"  key VARCHAR(255),"
			"  data BLOB,"

702 703 704
			"  PRIMARY KEY (chat_message_content_id, key),"
			"  FOREIGN KEY (chat_message_content_id)"
			"    REFERENCES chat_message_content(id)"
705 706 707
			"    ON DELETE CASCADE"
			")";

708
		*session <<
709
			"CREATE TABLE IF NOT EXISTS conference_message_crypto_data ("
710 711
			"  event_id" + primaryKeyRefStr("UNSIGNED BIGINT") + ","

712
			"  key VARCHAR(255),"
713 714
			"  data BLOB,"

Ronan's avatar
Ronan committed
715 716
			"  PRIMARY KEY (event_id, key),"
			"  FOREIGN KEY (event_id)"
717
			"    REFERENCES conference_chat_message_event(event_id)"
718
			"    ON DELETE CASCADE"
719
			")";
720 721 722 723

		// Trigger to delete participant_message cache entries.
		string displayedId = Utils::toString(static_cast<int>(ChatMessage::State::Displayed));
		string participantMessageDeleter =
724 725
			"CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter"
			"  AFTER UPDATE OF state ON chat_message_participant FOR EACH ROW"
726 727
			"  WHEN NEW.state = ";
		participantMessageDeleter += displayedId;
728
		participantMessageDeleter += " AND (SELECT COUNT(*) FROM ("
729 730
			"    SELECT state FROM chat_message_participant WHERE"
			"    NEW.event_id = chat_message_participant.event_id"
731 732 733 734 735
			"    AND state <> ";
		participantMessageDeleter += displayedId;
		participantMessageDeleter += "    LIMIT 1"
			"  )) = 0"
			"  BEGIN"
736 737
			"  DELETE FROM chat_message_participant WHERE NEW.event_id = chat_message_participant.event_id;"
			"  UPDATE conference_chat_message_event SET state = ";
738
		participantMessageDeleter += displayedId;
Ronan's avatar
Ronan committed
739
		participantMessageDeleter += " WHERE event_id = NEW.event_id;"
740 741 742
			"  END";

		*session << participantMessageDeleter;
Ronan's avatar
Ronan committed
743
	}
744

Ronan's avatar
Ronan committed
745
	bool MainDb::addEvent (const shared_ptr<EventLog> &eventLog) {
746 747
		L_D();

Ronan's avatar
Ronan committed
748 749
		if (!isConnected()) {
			lWarning() << "Unable to add event. Not connected.";
750
			return false;
Ronan's avatar
Ronan committed
751 752
		}

753 754 755 756 757 758
		bool soFarSoGood = false;

		L_BEGIN_LOG_EXCEPTION

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

Ronan's avatar
Ronan committed
759
		switch (eventLog->getType()) {
760
			case EventLog::Type::None:
Ronan's avatar
Ronan committed
761
				return false;
762

763 764 765
			case EventLog::Type::ConferenceCreated:
			case EventLog::Type::ConferenceDestroyed:
				d->insertConferenceEvent(eventLog);
766 767
				break;

768 769
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
770
				d->insertConferenceCallEvent(eventLog);
771
				break;
772

773 774
			case EventLog::Type::ConferenceChatMessage:
				d->insertConferenceChatMessageEvent(eventLog);
775 776
				break;

777 778 779 780
			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
781 782 783
				d->insertConferenceParticipantEvent(eventLog);
				break;

784 785
			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
786 787 788
				d->insertConferenceParticipantDeviceEvent(eventLog);
				break;

789
			case EventLog::Type::ConferenceSubjectChanged:
790
				d->insertConferenceSubjectEvent(eventLog);
Ronan's avatar
Ronan committed
791 792 793
				break;
		}

794 795 796 797 798 799 800
		tr.commit();

		soFarSoGood = true;

		L_END_LOG_EXCEPTION

		return soFarSoGood;
Ronan's avatar
Ronan committed
801 802
	}

Ronan's avatar
Ronan committed
803
	bool MainDb::deleteEvent (const shared_ptr<EventLog> &eventLog) {
Ronan's avatar
Ronan committed
804 805
		L_D();

Ronan's avatar
Ronan committed
806 807 808 809 810
		if (!isConnected()) {
			lWarning() << "Unable to delete event. Not connected.";
			return false;
		}

Ronan's avatar
Ronan committed
811
		long long &storageId = eventLog->getPrivate()->storageId;
Ronan's avatar
Ronan committed
812
		if (storageId < 0)
Ronan's avatar
Ronan committed
813 814 815 816 817
			return false;

		L_BEGIN_LOG_EXCEPTION

		soci::session *session = d->dbSession.getBackendSession<soci::session>();
Ronan's avatar
Ronan committed
818 819
		*session << "DELETE FROM event WHERE id = :id", soci::use(storageId);
		storageId = -1;
Ronan's avatar
Ronan committed
820 821 822

		L_END_LOG_EXCEPTION

Ronan's avatar
Ronan committed
823
		return storageId == -1;
Ronan's avatar
Ronan committed
824 825
	}

826
	void MainDb::cleanEvents (FilterMask mask) {
Ronan's avatar
Ronan committed
827 828
		L_D();

Ronan's avatar
Ronan committed
829 830 831 832 833
		if (!isConnected()) {
			lWarning() << "Unable to clean events. Not connected.";
			return;
		}

Ronan's avatar
Ronan committed
834
		string query = "DELETE FROM event" +
835
			buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter }, mask);
Ronan's avatar
Ronan committed
836 837 838 839 840 841 842

		L_BEGIN_LOG_EXCEPTION

		soci::session *session = d->dbSession.getBackendSession<soci::session>();
		*session << query;

		L_END_LOG_EXCEPTION
Ronan's avatar
Ronan committed
843 844
	}

845
	int MainDb::getEventsCount (FilterMask mask) const {
846
		L_D();
Ronan's avatar
Ronan committed