main-db.cpp 65.7 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-p.h"
31 32
#include "chat/chat-room/client-group-chat-room.h"
#include "conference/participant-p.h"
33
#include "content/content-type.h"
Ronan's avatar
Ronan committed
34
#include "content/content.h"
Ronan's avatar
Ronan committed
35
#include "core/core-p.h"
36
#include "db/session/db-session-provider.h"
Ronan's avatar
Ronan committed
37
#include "event-log/event-log-p.h"
38
#include "event-log/events.h"
Ronan's avatar
Ronan committed
39
#include "logger/logger.h"
40
#include "main-db-event-key-p.h"
Ronan's avatar
Ronan committed
41
#include "main-db-p.h"
Ronan's avatar
Ronan committed
42 43 44

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

45 46 47 48 49
// See: http://soci.sourceforge.net/doc/3.2/exchange.html
// Part: Object lifetime and immutability

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

Ronan's avatar
Ronan committed
50 51
using namespace std;

Ronan's avatar
Ronan committed
52 53 54 55
LINPHONE_BEGIN_NAMESPACE

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

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

58 59
#ifdef SOCI_ENABLED

Ronan's avatar
Ronan committed
60
// -----------------------------------------------------------------------------
61
// Soci backend.
Ronan's avatar
Ronan committed
62 63
// -----------------------------------------------------------------------------

64 65 66 67 68
	template<typename T>
	struct EnumToSql {
		T first;
		const char *second;
	};
69

70 71 72 73 74 75 76
	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)
		);
	}

77
	static constexpr EnumToSql<MainDb::Filter> eventFilterToSql[] = {
78
		{ MainDb::ConferenceCallFilter, "3, 4" },
Ronan's avatar
Ronan committed
79
		{ MainDb::ConferenceChatMessageFilter, "5" },
80
		{ MainDb::ConferenceInfoFilter, "1, 2, 6, 7, 8, 9, 10, 11, 12" }
81
	};
82

83
	static constexpr const char *mapEventFilterToSql (MainDb::Filter filter) {
84 85 86
		return mapEnumToSql(
			eventFilterToSql, sizeof eventFilterToSql / sizeof eventFilterToSql[0], filter
		);
87 88
	}

Ronan's avatar
Ronan committed
89 90
// -----------------------------------------------------------------------------

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

102
		if (mask == MainDb::NoFilter)
103 104 105 106 107 108 109 110 111 112
			return "";

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

			if (isStart) {
				isStart = false;
Ronan's avatar
Ronan committed
113
				sql += " " + condKeyWord + " type IN (";
114
			} else
Ronan's avatar
Ronan committed
115
				sql += ", ";
116 117 118
			sql += mapEventFilterToSql(filter);
		}

Ronan's avatar
Ronan committed
119 120 121
		if (!isStart)
			sql += ") ";

122 123 124
		return sql;
	}

125 126
// -----------------------------------------------------------------------------

127 128 129 130 131 132 133 134 135
	long long MainDbPrivate::resolveId (const soci::row &row, int col) const {
		L_Q();
		// See: http://soci.sourceforge.net/doc/master/backends/
		// `row id` is not supported by soci on Sqlite3. It's necessary to cast id to int...
		return q->getBackend() == AbstractDb::Sqlite3
			? static_cast<long long>(row.get<int>(0))
			: row.get<long long>(0);
	}

136
	long long MainDbPrivate::insertSipAddress (const string &sipAddress) {
137 138 139
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

140 141
		long long id = selectSipAddressId(sipAddress);
		if (id >= 0)
142 143
			return id;

Ronan's avatar
Ronan committed
144
		lInfo() << "Insert new sip address in database: `" << sipAddress << "`.";
145 146
		*session << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress);
		return q->getLastInsertId();
147 148
	}

149
	void MainDbPrivate::insertContent (long long eventId, const Content &content) {
150
		L_Q();
151 152
		soci::session *session = dbSession.getBackendSession<soci::session>();

153 154
		const long long &contentTypeId = insertContentType(content.getContentType().asString());
		const string &body = content.getBodyAsString();
155
		*session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES"
Ronan's avatar
Ronan committed
156
			"  (:eventId, :contentTypeId, :body)", soci::use(eventId), soci::use(contentTypeId),
157
			soci::use(body);
158

159
		const long long &messageContentId = q->getLastInsertId();
Sylvain Berfini's avatar
Sylvain Berfini committed
160
		if (content.getContentType().isFile()) {
161
			const FileContent &fileContent = static_cast<const FileContent &>(content);
162 163 164
			const string &fileName = fileContent.getFileName();
			const size_t &fileSize = fileContent.getFileSize();
			const string &filePath = fileContent.getFilePath();
Sylvain Berfini's avatar
Sylvain Berfini committed
165 166
			*session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES "
				" (:contentId, :name, :size, :path)",
167
				soci::use(messageContentId), soci::use(fileName), soci::use(fileSize), soci::use(filePath);
Sylvain Berfini's avatar
Sylvain Berfini committed
168 169
		}

170
		for (const auto &appData : content.getAppDataMap())
Ronan's avatar
Ronan committed
171 172
			*session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, name, data) VALUES"
				"  (:messageContentId, :name, :data)",
173
				soci::use(messageContentId), soci::use(appData.first), soci::use(appData.second);
174 175
	}

176 177 178
	void MainDbPrivate::updateContent (long long eventId, long long messageContentId, const Content &content) {
		soci::session *session = dbSession.getBackendSession<soci::session>();

179 180
		const long long &contentTypeId = insertContentType(content.getContentType().asString());
		const string &body = content.getBodyAsString();
181
		*session << "UPDATE chat_message_content SET content_type_id=:contentTypeId, body=:body WHERE event_id=:eventId",
182
			soci::use(contentTypeId), soci::use(body), soci::use(eventId);
183 184 185 186 187 188

		for (const auto &appData : content.getAppDataMap())
			*session << "UPDATE chat_message_content_app_data SET name=:name, data=:data WHERE chat_message_content_id=:messageContentId",
				soci::use(appData.first), soci::use(appData.second), soci::use(messageContentId);
	}

189
	long long MainDbPrivate::insertContentType (const string &contentType) {
190 191 192
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

193
		long long id;
194 195
		*session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(id);
		if (session->got_data())
196 197
			return id;

Ronan's avatar
Ronan committed
198
		lInfo() << "Insert new content type in database: `" << contentType << "`.";
199 200
		*session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType);
		return q->getLastInsertId();
201 202
	}

203 204 205 206
	long long MainDbPrivate::insertChatRoom (
		long long peerSipAddressId,
		long long localSipAddressId,
		int capabilities,
207
		const tm &creationTime
208 209 210
	) {
		L_Q();

211 212
		soci::session *session = dbSession.getBackendSession<soci::session>();

213
		long long id = selectChatRoomId(peerSipAddressId, localSipAddressId);
214 215
		if (id >= 0)
			return id;
216

217 218 219 220 221 222 223 224 225
		lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId <<
			", local=" << localSipAddressId << ", capabilities=" << capabilities << ").";
		*session << "INSERT INTO chat_room ("
			"  peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities"
			") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities)",
			soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime),
			soci::use(capabilities);

		return q->getLastInsertId();
226 227
	}

228
	long long MainDbPrivate::insertChatRoom (const shared_ptr<ChatRoom> &chatRoom) {
229 230 231 232 233
		L_Q();

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

		const ChatRoomId &chatRoomId = chatRoom->getChatRoomId();
234 235
		const long long &peerSipAddressId = insertSipAddress(chatRoomId.getPeerAddress().asString());
		const long long &localSipAddressId = insertSipAddress(chatRoomId.getLocalAddress().asString());
236 237 238 239 240 241 242 243 244 245 246

		long long id = selectChatRoomId(peerSipAddressId, localSipAddressId);
		if (id >= 0) {
			lWarning() << "Unable to insert chat room (it already exists): (peer=" << peerSipAddressId <<
				", local=" << localSipAddressId << ").";
			return id;
		}

		lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId <<
			", local=" << localSipAddressId << ").";

247 248 249
		const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime());
		const int &capabilities = static_cast<int>(chatRoom->getCapabilities());
		const string &subject = chatRoom->getSubject();
250 251 252 253
		*session << "INSERT INTO chat_room ("
			"  peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities, subject"
			") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities, :subject)",
			soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime),
254
			soci::use(capabilities), soci::use(subject);
255 256

		id = q->getLastInsertId();
257 258 259 260

		if (!chatRoom->canHandleParticipants())
			return id;

Ronan's avatar
Ronan committed
261 262
		shared_ptr<Participant> me = chatRoom->getMe();
		insertChatRoomParticipant(id, insertSipAddress(me->getAddress().asString()), me->isAdmin());
263
		for (const auto &participant : chatRoom->getParticipants())
264
			insertChatRoomParticipant(id, insertSipAddress(participant->getAddress().asString()), participant->isAdmin());
265 266

		return id;
267 268
	}

269
	void MainDbPrivate::insertChatRoomParticipant (long long chatRoomId, long long sipAddressId, bool isAdmin) {
270 271
		// See: https://stackoverflow.com/a/15299655 (cast to reference)

272
		soci::session *session = dbSession.getBackendSession<soci::session>();
273 274
		soci::statement statement = (
			session->prepare << "UPDATE chat_room_participant SET is_admin = :isAdmin"
275
				"  WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :sipAddressId",
276
				soci::use(static_cast<const int &>(isAdmin)), soci::use(chatRoomId), soci::use(sipAddressId)
277 278
		);
		statement.execute(true);
Ronan's avatar
Ronan committed
279 280
		if (statement.get_affected_rows() == 0) {
			lInfo() << "Insert new chat room participant in database: `" << sipAddressId << "` (isAdmin=" << isAdmin << ").";
281
			*session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)"
282
				"  VALUES (:chatRoomId, :sipAddressId, :isAdmin)",
283
				soci::use(chatRoomId), soci::use(sipAddressId), soci::use(static_cast<const int &>(isAdmin));
Ronan's avatar
Ronan committed
284
		}
285 286
	}

287
	void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) {
288 289
		soci::session *session = dbSession.getBackendSession<soci::session>();
		soci::statement statement = (
290
			session->prepare << "UPDATE chat_message_participant SET state = :state"
291
				"  WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId",
Ronan's avatar
Ronan committed
292
				soci::use(state), soci::use(eventId), soci::use(sipAddressId)
293 294
		);
		statement.execute(true);
295
		if (statement.get_affected_rows() == 0 && state != static_cast<int>(ChatMessage::State::Displayed))
296
			*session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)"
Ronan's avatar
Ronan committed
297 298
				"  VALUES (:eventId, :sipAddressId, :state)",
				soci::use(eventId), soci::use(sipAddressId), soci::use(state);
299 300
	}

301
	long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const {
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
		soci::session *session = dbSession.getBackendSession<soci::session>();

		long long id;
		*session << "SELECT id FROM sip_address WHERE value = :sipAddress", soci::use(sipAddress), soci::into(id);
		return session->got_data() ? id : -1;
	}

	long long MainDbPrivate::selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const {
		soci::session *session = dbSession.getBackendSession<soci::session>();

		long long id;
		*session << "SELECT id FROM chat_room"
			"  WHERE peer_sip_address_id = :peerSipAddressId AND local_sip_address_id = :localSipAddressId",
			soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::into(id);
		return session->got_data() ? id : -1;
	}

	long long MainDbPrivate::selectChatRoomId (const ChatRoomId &chatRoomId) const {
		long long peerSipAddressId = selectSipAddressId(chatRoomId.getPeerAddress().asString());
		if (peerSipAddressId < 0)
			return -1;

		long long localSipAddressId = selectSipAddressId(chatRoomId.getLocalAddress().asString());
		if (localSipAddressId < 0)
			return -1;

		return selectChatRoomId(peerSipAddressId, localSipAddressId);
	}

331 332 333 334 335 336 337
	void MainDbPrivate::deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId) {
		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "DELETE FROM chat_room_participant"
			"  WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId",
			soci::use(chatRoomId), soci::use(participantSipAddressId);
	}

Ronan's avatar
Ronan committed
338 339
// -----------------------------------------------------------------------------

340
	shared_ptr<EventLog> MainDbPrivate::selectGenericConferenceEvent (
341
		long long eventId,
342
		EventLog::Type type,
343
		time_t creationTime,
344
		const ChatRoomId &chatRoomId
345
	) const {
Ronan's avatar
Ronan committed
346 347 348 349 350 351
		switch (type) {
			case EventLog::Type::None:
				return nullptr;

			case EventLog::Type::ConferenceCreated:
			case EventLog::Type::ConferenceDestroyed:
352
				return selectConferenceEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
353

354 355
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
356
				return selectConferenceCallEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
357 358

			case EventLog::Type::ConferenceChatMessage:
359
				return selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
360 361 362 363 364

			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
365
				return selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
366 367 368

			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
369
				return selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
370 371

			case EventLog::Type::ConferenceSubjectChanged:
372
				return selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId);
Ronan's avatar
Ronan committed
373 374 375 376 377
		}

		return nullptr;
	}

378
	shared_ptr<EventLog> MainDbPrivate::selectConferenceEvent (
379
		long long,
380
		EventLog::Type type,
381
		time_t creationTime,
382
		const ChatRoomId &chatRoomId
383
	) const {
384 385
		return make_shared<ConferenceEvent>(
			type,
386
			creationTime,
387
			chatRoomId
388
		);
Ronan's avatar
Ronan committed
389 390
	}

391
	shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
392
		long long eventId,
393
		EventLog::Type type,
394
		time_t creationTime,
395
		const ChatRoomId &chatRoomId
396
	) const {
Ronan's avatar
Ronan committed
397 398 399 400
		// TODO.
		return nullptr;
	}

401
	shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
402
		long long eventId,
403
		EventLog::Type type,
404
		time_t creationTime,
405
		const ChatRoomId &chatRoomId
406
	) const {
Ronan's avatar
Ronan committed
407 408 409 410
		L_Q();

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

411
		shared_ptr<ChatRoom> chatRoom = core->findChatRoom(chatRoomId);
Ronan's avatar
Ronan committed
412 413 414 415 416
		if (!chatRoom)
			return nullptr;

		// TODO: Use cache, do not fetch the same message twice.

417
		// 1 - Fetch chat message.
418
		shared_ptr<ChatMessage> chatMessage;
419
		{
420 421
			string fromSipAddress;
			string toSipAddress;
422 423 424

			tm messageTime;

425
			string imdnMessageId;
426

427 428 429 430 431
			int state;
			int direction;
			int isSecured;

			soci::session *session = dbSession.getBackendSession<soci::session>();
432
			*session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured"
433 434
				"  FROM event, conference_chat_message_event, sip_address AS from_sip_address,"
				"  sip_address AS to_sip_address"
435 436
				"  WHERE event_id = :eventId"
				"  AND event_id = event.id"
437 438
				"  AND from_sip_address_id = from_sip_address.id"
				"  AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress),
439 440
				soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction),
				soci::into(isSecured), soci::use(eventId);
441

442 443 444 445
			chatMessage = shared_ptr<ChatMessage>(new ChatMessage(
				chatRoom,
				static_cast<ChatMessage::Direction>(direction)
			));
446 447 448
			chatMessage->getPrivate()->setState(static_cast<ChatMessage::State>(state), true);
			chatMessage->setIsSecured(static_cast<bool>(isSecured));

449 450
			chatMessage->getPrivate()->forceFromAddress(IdentityAddress(fromSipAddress));
			chatMessage->getPrivate()->forceToAddress(IdentityAddress(toSipAddress));
451 452

			chatMessage->getPrivate()->setTime(Utils::getTmAsTimeT(messageTime));
453
		}
Ronan's avatar
Ronan committed
454

455 456 457
		// 2 - Fetch contents.
		{
			soci::session *session = dbSession.getBackendSession<soci::session>();
458
			const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body FROM chat_message_content, content_type"
459 460 461
				"  WHERE event_id = :eventId AND content_type_id = content_type.id";
			soci::rowset<soci::row> rows = (session->prepare << query, soci::use(eventId));
			for (const auto &row : rows) {
462
				ContentType contentType(row.get<string>(2));
463 464 465 466 467
				Content *content;

				if (contentType == ContentType::FileTransfer)
					content = new FileTransferContent();
				else if (contentType.isFile()) {
468
					const long long &contentId = resolveId(row, 0);
469 470 471 472 473 474

					string name;
					int size;
					string path;

					*session << "SELECT name, size, path FROM chat_message_file_content"
Ronan's avatar
Ronan committed
475
						"  WHERE chat_message_content_id = :contentId",
Ronan's avatar
Ronan committed
476
						soci::into(name), soci::into(size), soci::into(path), soci::use(contentId);
477 478 479 480 481 482 483 484

					FileContent *fileContent = new FileContent();
					fileContent->setFileName(name);
					fileContent->setFileSize(static_cast<size_t>(size));
					fileContent->setFilePath(path);

					content = fileContent;
				} else
Ronan's avatar
Ronan committed
485
					content = new Content();
486 487

				content->setContentType(contentType);
Sylvain Berfini's avatar
Sylvain Berfini committed
488
				content->setBody(row.get<string>(3));
489
				chatMessage->addContent(*content);
490
			}
Ronan's avatar
Ronan committed
491
		}
492 493 494

		// TODO: Use cache.
		return make_shared<ConferenceChatMessageEvent>(
495
			creationTime,
496 497
			chatMessage
		);
Ronan's avatar
Ronan committed
498 499
	}

500
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
501
		long long eventId,
502
		EventLog::Type type,
503
		time_t creationTime,
504
		const ChatRoomId &chatRoomId
505
	) const {
506 507 508 509
		unsigned int notifyId;
		string participantAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
510 511
		*session << "SELECT notify_id, participant_address.value"
			"  FROM conference_notified_event, conference_participant_event, sip_address as participant_address"
512
			"  WHERE conference_participant_event.event_id = :eventId"
Ronan's avatar
Ronan committed
513
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
514
			"    AND participant_address.id = participant_sip_address_id",
515 516 517 518
			soci::into(notifyId), soci::into(participantAddress), soci::use(eventId);

		return make_shared<ConferenceParticipantEvent>(
			type,
519
			creationTime,
520
			chatRoomId,
521
			notifyId,
522
			IdentityAddress(participantAddress)
523
		);
Ronan's avatar
Ronan committed
524 525
	}

526
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantDeviceEvent (
527
		long long eventId,
528
		EventLog::Type type,
529
		time_t creationTime,
530
		const ChatRoomId &chatRoomId
531
	) const {
532 533
		unsigned int notifyId;
		string participantAddress;
534
		string deviceAddress;
535 536

		soci::session *session = dbSession.getBackendSession<soci::session>();
537
		*session << "SELECT notify_id, participant_address.value, device_address.value"
538
			"  FROM conference_notified_event, conference_participant_event, conference_participant_device_event,"
539
			"    sip_address AS participant_address, sip_address AS device_address"
540 541
			"  WHERE conference_participant_device_event.event_id = :eventId"
			"    AND conference_participant_event.event_id = conference_participant_device_event.event_id"
542
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
543 544
			"    AND participant_address.id = participant_sip_address_id"
			"    AND device_address.id = device_sip_address_id",
545
			soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId);
546 547 548

		return make_shared<ConferenceParticipantDeviceEvent>(
			type,
549
			creationTime,
550
			chatRoomId,
551
			notifyId,
552 553
			IdentityAddress(participantAddress),
			IdentityAddress(deviceAddress)
554
		);
Ronan's avatar
Ronan committed
555 556
	}

557
	shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
558
		long long eventId,
559
		EventLog::Type type,
560
		time_t creationTime,
561
		const ChatRoomId &chatRoomId
562 563 564 565
	) const {
		unsigned int notifyId;
		string subject;

566 567 568 569 570 571
		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);
572 573

		return make_shared<ConferenceSubjectEvent>(
574
			creationTime,
575
			chatRoomId,
576 577 578
			notifyId,
			subject
		);
Ronan's avatar
Ronan committed
579 580
	}

581 582
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
583
	long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
584 585 586
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

587 588
		const int &type = static_cast<int>(eventLog->getType());
		const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
589
		*session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)",
590 591 592
			soci::use(type),
			soci::use(creationTime);

593
		return q->getLastInsertId();
594 595
	}

Ronan's avatar
Ronan committed
596
	long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
597 598 599
		shared_ptr<ConferenceEvent> conferenceEvent = static_pointer_cast<ConferenceEvent>(eventLog);

		long long eventId = -1;
600
		const long long &curChatRoomId = selectChatRoomId(conferenceEvent->getChatRoomId());
601 602 603 604 605 606 607 608 609
		if (curChatRoomId < 0) {
			// A conference event can be inserted in database only if chat room exists.
			// Otherwise it's an error.
			const ChatRoomId &chatRoomId = conferenceEvent->getChatRoomId();
			lError() << "Unable to find chat room storage id of (peer=" +
				chatRoomId.getPeerAddress().asString() +
				", local=" + chatRoomId.getLocalAddress().asString() + "`).";
		} else {
			eventId = insertEvent(eventLog);
610

611 612 613 614
			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);

615
			const tm &lastUpdateTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
616
			*session << "UPDATE chat_room SET last_update_time = :lastUpdateTime"
617
				"  WHERE id = :chatRoomId", soci::use(lastUpdateTime),
618 619
				soci::use(curChatRoomId);
		}
620 621 622 623 624 625 626

		if (chatRoomId)
			*chatRoomId = curChatRoomId;

		return eventId;
	}

Ronan's avatar
Ronan committed
627
	long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
628 629 630 631
		// TODO.
		return 0;
	}

Ronan's avatar
Ronan committed
632 633
	long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
634 635 636 637 638 639
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return -1;
		}

640
		const long long &eventId = insertConferenceEvent(eventLog);
641 642
		if (eventId < 0)
			return -1;
643

644
		soci::session *session = dbSession.getBackendSession<soci::session>();
645 646 647 648 649 650 651 652 653

		const long long &fromSipAddressId = insertSipAddress(chatMessage->getFromAddress().asString());
		const long long &toSipAddressId = insertSipAddress(chatMessage->getToAddress().asString());
		const tm &messageTime = Utils::getTimeTAsTm(chatMessage->getTime());
		const int &state = static_cast<int>(chatMessage->getState());
		const int &direction = static_cast<int>(chatMessage->getDirection());
		const string &imdnMessageId = chatMessage->getImdnMessageId();
		const int &isSecured = chatMessage->isSecured() ? 1 : 0;

654
		*session << "INSERT INTO conference_chat_message_event ("
655
			"  event_id, from_sip_address_id, to_sip_address_id,"
656
			"  time, state, direction, imdn_message_id, is_secured"
657 658
			") VALUES ("
			"  :eventId, :localSipaddressId, :remoteSipaddressId,"
659
			"  :time, :state, :direction, :imdnMessageId, :isSecured"
660
			")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId),
661 662
			soci::use(messageTime), soci::use(state), soci::use(direction),
			soci::use(imdnMessageId), soci::use(isSecured);
663

664
		for (const Content *content : chatMessage->getContents())
665
			insertContent(eventId, *content);
666

667 668 669
		return eventId;
	}

670
	void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
671 672 673 674 675 676 677 678 679
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return;
		}

		const EventLogPrivate *dEventLog = eventLog->getPrivate();
		MainDbEventKeyPrivate *dEventKey = dEventLog->dbKey.getPrivate();
680
		const long long &eventId = dEventKey->storageId;
681 682

		soci::session *session = dbSession.getBackendSession<soci::session>();
683 684 685
		const int &state = static_cast<int>(chatMessage->getState());
		*session << "UPDATE conference_chat_message_event SET state = :state WHERE event_id = :eventId",
			soci::use(state), soci::use(eventId);
686 687 688 689 690 691

		/*for (const Content *content : chatMessage->getContents())
			updateContent(eventId, *content);*/
		//TODO check if content needs to be inserted, updated or removed
	}

692 693
	long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
		long long curChatRoomId;
694
		const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId);
695 696 697
		if (eventId < 0)
			return -1;

698
		const unsigned int &lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
699 700 701

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
Ronan's avatar
Ronan committed
702 703
			"  VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId);
		*session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE peer_sip_address_id = :chatRoomId",
704 705 706 707
			soci::use(lastNotifyId), soci::use(curChatRoomId);

		if (chatRoomId)
			*chatRoomId = curChatRoomId;
708

709 710 711
		return eventId;
	}

Ronan's avatar
Ronan committed
712
	long long MainDbPrivate::insertConferenceParticipantEvent (const shared_ptr<EventLog> &eventLog) {
713
		long long chatRoomId;
714
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
715 716 717
		if (eventId < 0)
			return -1;

718 719 720
		shared_ptr<ConferenceParticipantEvent> participantEvent =
			static_pointer_cast<ConferenceParticipantEvent>(eventLog);

721
		const long long &participantAddressId = insertSipAddress(
722
			participantEvent->getParticipantAddress().asString()
723 724
		);

725
		soci::session *session = dbSession.getBackendSession<soci::session>();
726
		*session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)"
727
			"  VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
728

729 730 731 732 733 734 735 736 737
		bool isAdmin = eventLog->getType() == EventLog::Type::ConferenceParticipantSetAdmin;
		switch (eventLog->getType()) {
			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
				insertChatRoomParticipant(chatRoomId, participantAddressId, isAdmin);
				break;

			case EventLog::Type::ConferenceParticipantRemoved:
738 739
				deleteChatRoomParticipant(chatRoomId, participantAddressId);
				break;
740 741 742 743 744

			default:
				break;
		}

745 746 747
		return eventId;
	}

Ronan's avatar
Ronan committed
748
	long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
749
		const long long &eventId = insertConferenceParticipantEvent(eventLog);
750 751 752
		if (eventId < 0)
			return -1;

753
		const long long &deviceAddressId = insertSipAddress(
754
			static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog)->getDeviceAddress().asString()
755 756 757
		);

		soci::session *session = dbSession.getBackendSession<soci::session>();
758
		*session << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)"
759
			"  VALUES (:eventId, :deviceAddressId)", soci::use(eventId), soci::use(deviceAddressId);
760

761
		return eventId;
762 763
	}

Ronan's avatar
Ronan committed
764
	long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
765
		long long chatRoomId;
766
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
767 768 769 770
		if (eventId < 0)
			return -1;

		const string &subject = static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject();
771 772

		soci::session *session = dbSession.getBackendSession<soci::session>();
773
		*session << "INSERT INTO conference_subject_event (event_id, subject)"
774 775 776 777
			"  VALUES (:eventId, :subject)", soci::use(eventId), soci::use(subject);

		*session << "UPDATE chat_room SET subject = :subject"
			"  WHERE id = :chatRoomId", soci::use(subject), soci::use(chatRoomId);
778

779
		return eventId;
780 781
	}

782 783 784 785 786 787 788 789 790 791 792 793 794
// -----------------------------------------------------------------------------

	shared_ptr<EventLog> MainDbPrivate::getEventFromCache (long long eventId) const {
		auto it = storageIdToEvent.find(eventId);
		if (it == storageIdToEvent.cend())
			return nullptr;

		shared_ptr<EventLog> eventLog = it->second.lock();
		// Must exist. If not, implementation bug.
		L_ASSERT(eventLog);
		return eventLog;
	}

795
	void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) {
796
		soci::session *session = dbSession.getBackendSession<soci::session>();
797
		soci::rowset<soci::row> rows = (session->prepare << query, soci::use(chatRoomId));
798
		for (const auto &row : rows) {
799
			shared_ptr<EventLog> eventLog = getEventFromCache(resolveId(row, 0));
800 801 802 803 804 805 806 807
			if (eventLog) {
				const EventLogPrivate *dEventLog = eventLog->getPrivate();
				L_ASSERT(dEventLog->dbKey.isValid());
				dEventLog->dbKey = MainDbEventKey();
			}
		}
	}