main-db.cpp 104 KB
Newer Older
Ronan's avatar
Ronan committed
1
/*
2
 * main-db.cpp
3
 * Copyright (C) 2010-2018 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 <ctime>
21

22
#include "linphone/utils/algorithm.h"
Ronan's avatar
Ronan committed
23
#include "linphone/utils/static-string.h"
24

Ronan's avatar
Ronan committed
25
#include "chat/chat-message/chat-message-p.h"
26
#include "chat/chat-room/chat-room-p.h"
27
#include "chat/chat-room/client-group-chat-room.h"
28
#include "chat/chat-room/server-group-chat-room.h"
29
#include "conference/participant-device.h"
30
#include "conference/participant-p.h"
31
#include "core/core-p.h"
32
#include "event-log/event-log-p.h"
33
#include "event-log/events.h"
34
#include "main-db-key-p.h"
Ronan's avatar
Ronan committed
35

36 37
#include "internal/db-transaction.h"
#include "internal/statements.h"
38

Ronan's avatar
Ronan committed
39 40
// =============================================================================

41 42 43 44 45
// See: http://soci.sourceforge.net/doc/3.2/exchange.html
// Part: Object lifetime and immutability

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

46 47
using namespace std;

Ronan's avatar
Ronan committed
48 49
LINPHONE_BEGIN_NAMESPACE

50
namespace {
51
	constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 7);
52 53 54
	constexpr unsigned int ModuleVersionFriends = makeVersion(1, 0, 0);
	constexpr unsigned int ModuleVersionLegacyFriendsImport = makeVersion(1, 0, 0);
	constexpr unsigned int ModuleVersionLegacyHistoryImport = makeVersion(1, 0, 0);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

	constexpr int LegacyFriendListColId = 0;
	constexpr int LegacyFriendListColName = 1;
	constexpr int LegacyFriendListColRlsUri = 2;
	constexpr int LegacyFriendListColSyncUri = 3;
	constexpr int LegacyFriendListColRevision = 4;

	constexpr int LegacyFriendColFriendListId = 1;
	constexpr int LegacyFriendColSipAddress = 2;
	constexpr int LegacyFriendColSubscribePolicy = 3;
	constexpr int LegacyFriendColSendSubscribe = 4;
	constexpr int LegacyFriendColRefKey = 5;
	constexpr int LegacyFriendColVCard = 6;
	constexpr int LegacyFriendColVCardEtag = 7;
	constexpr int LegacyFriendColVCardSyncUri = 8;
	constexpr int LegacyFriendColPresenceReceived = 9;

	constexpr int LegacyMessageColLocalAddress = 1;
	constexpr int LegacyMessageColRemoteAddress = 2;
	constexpr int LegacyMessageColDirection = 3;
	constexpr int LegacyMessageColText = 4;
	constexpr int LegacyMessageColState = 7;
	constexpr int LegacyMessageColUrl = 8;
	constexpr int LegacyMessageColDate = 9;
	constexpr int LegacyMessageColAppData = 10;
	constexpr int LegacyMessageColContentId = 11;
	constexpr int LegacyMessageColContentType = 13;
	constexpr int LegacyMessageColIsSecured = 14;
83 84
}

85 86
// -----------------------------------------------------------------------------
// soci helpers.
Ronan's avatar
Ronan committed
87 88
// -----------------------------------------------------------------------------

89 90 91 92 93
static inline vector<char> blobToVector (soci::blob &in) {
	size_t len = in.get_len();
	if (!len)
		return vector<char>();
	vector<char> out(len);
94
	in.read(0, &out[0], len);
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
	return out;
}

static inline string blobToString (soci::blob &in) {
	vector<char> out = blobToVector(in);
	return string(out.begin(), out.end());
}

static constexpr string &blobToString (string &in) {
	return in;
}

template<typename T>
static T getValueFromRow (const soci::row &row, int index, bool &isNull) {
	isNull = false;
Ronan's avatar
Ronan committed
110

111 112 113 114 115 116
	if (row.get_indicator(size_t(index)) == soci::i_null) {
		isNull = true;
		return T();
	}
	return row.get<T>(size_t(index));
}
117

118
// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
119
// Event filter tools.
120 121
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
122
// Some tools to build filters at compile time.
123 124 125 126 127 128 129 130 131 132 133 134
template<typename T>
struct EnumToSql {
	T first;
	const char *second;
};

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)
	);
}
135

Ronan's avatar
Ronan committed
136 137 138 139 140
template<EventLog::Type ...Type>
struct SqlEventFilterBuilder {};

template<EventLog::Type Type, EventLog::Type... List>
struct SqlEventFilterBuilder<Type, List...> {
141 142 143
	static constexpr auto get () L_AUTO_RETURN(
		StaticIntString<int(Type)>() + "," + SqlEventFilterBuilder<List...>::get()
	);
Ronan's avatar
Ronan committed
144 145 146 147
};

template<EventLog::Type Type>
struct SqlEventFilterBuilder<Type> {
148
	static constexpr auto get () L_AUTO_RETURN(StaticIntString<int(Type)>());
149
};
150

Ronan's avatar
Ronan committed
151 152 153 154 155
// -----------------------------------------------------------------------------
// Event filters.
// -----------------------------------------------------------------------------

namespace {
156 157 158 159
	#ifdef _WIN32
		// TODO: Find a workaround to deal with StaticString concatenation!!!
		constexpr char ConferenceCallFilter[] = "3,4";
		constexpr char ConferenceChatMessageFilter[] = "5";
160
		constexpr char ConferenceInfoNoDeviceFilter[] = "1,2,6,7,8,9,12,13";
161
		constexpr char ConferenceInfoFilter[] = "1,2,6,7,8,9,10,11,12";
162
		constexpr char ConferenceChatMessageSecurityFilter[] = "5,13";
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	#else
		constexpr auto ConferenceCallFilter = SqlEventFilterBuilder<
			EventLog::Type::ConferenceCallStart,
			EventLog::Type::ConferenceCallEnd
		>::get();

		constexpr auto ConferenceChatMessageFilter = SqlEventFilterBuilder<EventLog::Type::ConferenceChatMessage>::get();

		constexpr auto ConferenceInfoNoDeviceFilter = SqlEventFilterBuilder<
			EventLog::Type::ConferenceCreated,
			EventLog::Type::ConferenceTerminated,
			EventLog::Type::ConferenceParticipantAdded,
			EventLog::Type::ConferenceParticipantRemoved,
			EventLog::Type::ConferenceParticipantSetAdmin,
			EventLog::Type::ConferenceParticipantUnsetAdmin,
178 179
			EventLog::Type::ConferenceSubjectChanged,
			EventLog::Type::ConferenceSecurityEvent
180 181 182 183 184 185
		>::get();

		constexpr auto ConferenceInfoFilter = ConferenceInfoNoDeviceFilter + "," + SqlEventFilterBuilder<
			EventLog::Type::ConferenceParticipantDeviceAdded,
			EventLog::Type::ConferenceParticipantDeviceRemoved
		>::get();
186 187 188 189

		constexpr auto ConferenceChatMessageSecurityFilter = ConferenceChatMessageFilter + "," + SqlEventFilterBuilder<
			EventLog::Type::ConferenceSecurityEvent
		>::get();
190
	#endif // ifdef _WIN32
Ronan's avatar
Ronan committed
191 192 193 194 195

	constexpr EnumToSql<MainDb::Filter> EventFilterToSql[] = {
		{ MainDb::ConferenceCallFilter, ConferenceCallFilter },
		{ MainDb::ConferenceChatMessageFilter, ConferenceChatMessageFilter },
		{ MainDb::ConferenceInfoNoDeviceFilter, ConferenceInfoNoDeviceFilter },
196 197
		{ MainDb::ConferenceInfoFilter, ConferenceInfoFilter },
		{ MainDb::ConferenceChatMessageSecurityFilter, ConferenceChatMessageSecurityFilter }
Ronan's avatar
Ronan committed
198 199 200 201
	};
}

static const char *mapEventFilterToSql (MainDb::Filter filter) {
202
	return mapEnumToSql(
Ronan's avatar
Ronan committed
203
		EventFilterToSql, sizeof EventFilterToSql / sizeof EventFilterToSql[0], filter
204 205
	);
}
206

207 208
// -----------------------------------------------------------------------------

209 210 211 212 213
static string buildSqlEventFilter (
	const list<MainDb::Filter> &filters,
	MainDb::FilterMask mask,
	const string &condKeyWord = "WHERE"
) {
214
	L_ASSERT(findIf(filters, [](const MainDb::Filter &filter) { return filter == MainDb::NoFilter; }) == filters.cend());
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

	if (mask == MainDb::NoFilter)
		return "";

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

		if (isStart) {
			isStart = false;
			sql += " " + condKeyWord + " type IN (";
		} else
			sql += ", ";
		sql += mapEventFilterToSql(filter);
	}

	if (!isStart)
		sql += ") ";

	return sql;
}
238

239 240
// -----------------------------------------------------------------------------
// Misc helpers.
241 242
// -----------------------------------------------------------------------------

243
shared_ptr<AbstractChatRoom> MainDbPrivate::findChatRoom (const ConferenceId &conferenceId) const {
244
	L_Q();
245
	shared_ptr<AbstractChatRoom> chatRoom = q->getCore()->findChatRoom(conferenceId);
246
	if (!chatRoom)
247
		lError() << "Unable to find chat room: " << conferenceId << ".";
248
	return chatRoom;
249 250
}

251 252 253
// -----------------------------------------------------------------------------
// Low level API.
// -----------------------------------------------------------------------------
254

255
long long MainDbPrivate::insertSipAddress (const string &sipAddress) {
256 257 258
	long long sipAddressId = selectSipAddressId(sipAddress);
	if (sipAddressId >= 0)
		return sipAddressId;
259

260
	lInfo() << "Insert new sip address in database: `" << sipAddress << "`.";
261
	*dbSession.getBackendSession() << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress);
262
	return dbSession.getLastInsertId();
263
}
264

265
void MainDbPrivate::insertContent (long long chatMessageId, const Content &content) {
266
	soci::session *session = dbSession.getBackendSession();
267 268 269 270

	const long long &contentTypeId = insertContentType(content.getContentType().asString());
	const string &body = content.getBodyAsString();
	*session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES"
271
		" (:chatMessageId, :contentTypeId, :body)", soci::use(chatMessageId), soci::use(contentTypeId),
272 273
		soci::use(body);

274
	const long long &chatMessageContentId = dbSession.getLastInsertId();
275
	if (content.isFile()) {
276 277 278 279
		const FileContent &fileContent = static_cast<const FileContent &>(content);
		const string &name = fileContent.getFileName();
		const size_t &size = fileContent.getFileSize();
		const string &path = fileContent.getFilePath();
280
		*session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES"
281 282 283 284 285 286 287 288 289
			" (:chatMessageContentId, :name, :size, :path)",
			soci::use(chatMessageContentId), soci::use(name), soci::use(size), soci::use(path);
	}

	for (const auto &appData : content.getAppDataMap())
		*session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, name, data) VALUES"
			" (:chatMessageContentId, :name, :data)",
			soci::use(chatMessageContentId), soci::use(appData.first), soci::use(appData.second);
}
290

291
long long MainDbPrivate::insertContentType (const string &contentType) {
292
	soci::session *session = dbSession.getBackendSession();
293

294 295
	long long contentTypeId;
	*session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(contentTypeId);
296
	if (session->got_data())
297
		return contentTypeId;
298

299 300
	lInfo() << "Insert new content type in database: `" << contentType << "`.";
	*session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType);
301
	return dbSession.getLastInsertId();
302 303 304 305 306 307 308
}

long long MainDbPrivate::insertOrUpdateImportedBasicChatRoom (
	long long peerSipAddressId,
	long long localSipAddressId,
	const tm &creationTime
) {
309
	soci::session *session = dbSession.getBackendSession();
310

311 312 313 314 315
	long long chatRoomId = selectChatRoomId(peerSipAddressId, localSipAddressId);
	if (chatRoomId >= 0) {
		*session << "UPDATE chat_room SET last_update_time = :lastUpdateTime WHERE id = :chatRoomId",
			soci::use(creationTime), soci::use(chatRoomId);
		return chatRoomId;
316 317
	}

Benjamin REIS's avatar
Benjamin REIS committed
318
	const int capabilities = ChatRoom::CapabilitiesMask(
319 320 321 322 323 324 325 326 327
		{ ChatRoom::Capabilities::Basic, ChatRoom::Capabilities::Migratable }
	);
	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);
328

329
	return dbSession.getLastInsertId();
330
}
331

332
long long MainDbPrivate::insertChatRoom (const shared_ptr<AbstractChatRoom> &chatRoom, unsigned int notifyId) {
333 334 335
	const ConferenceId &conferenceId = chatRoom->getConferenceId();
	const long long &peerSipAddressId = insertSipAddress(conferenceId.getPeerAddress().asString());
	const long long &localSipAddressId = insertSipAddress(conferenceId.getLocalAddress().asString());
336

337 338
	long long chatRoomId = selectChatRoomId(peerSipAddressId, localSipAddressId);
	if (chatRoomId >= 0) {
339
		// The chat room is already stored in DB, but still update the notify id that might have changed
340
		lInfo() << "Update chat room in database: " << conferenceId << ".";
341
		*dbSession.getBackendSession() << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId",
342
			soci::use(notifyId), soci::use(chatRoomId);
343 344 345 346 347 348 349 350 351 352 353 354 355
	} else {
		
		lInfo() << "Insert new chat room in database: " << conferenceId << ".";
		
		const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime());
		const tm &lastUpdateTime = Utils::getTimeTAsTm(chatRoom->getLastUpdateTime());
		
		// Remove capabilities like `Proxy`.
		const int &capabilities = chatRoom->getCapabilities() & ~ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Proxy);
		
		const string &subject = chatRoom->getSubject();
		const int &flags = chatRoom->hasBeenLeft();
		*dbSession.getBackendSession() << "INSERT INTO chat_room ("
356 357 358 359 360 361 362 363
		"  peer_sip_address_id, local_sip_address_id, creation_time,"
		"  last_update_time, capabilities, subject, flags, last_notify_id"
		") VALUES ("
		"  :peerSipAddressId, :localSipAddressId, :creationTime,"
		"  :lastUpdateTime, :capabilities, :subject, :flags, :lastNotifyId"
		")",
		soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime),
		soci::use(lastUpdateTime), soci::use(capabilities), soci::use(subject), soci::use(flags), soci::use(notifyId);
364 365 366
		
		chatRoomId = dbSession.getLastInsertId();
	}
367
	// Do not add 'me' when creating a server-group-chat-room.
368
	if (conferenceId.getLocalAddress() != conferenceId.getPeerAddress()) {
369 370
		shared_ptr<Participant> me = chatRoom->getMe();
		long long meId = insertChatRoomParticipant(
371
			chatRoomId,
372 373 374 375
			insertSipAddress(me->getAddress().asString()),
			me->isAdmin()
		);
		for (const auto &device : me->getPrivate()->getDevices())
376
			insertChatRoomParticipantDevice(meId, insertSipAddress(device->getAddress().asString()), device->getName());
377
	}
378

379 380
	for (const auto &participant : chatRoom->getParticipants()) {
		long long participantId = insertChatRoomParticipant(
381
			chatRoomId,
382 383 384 385
			insertSipAddress(participant->getAddress().asString()),
			participant->isAdmin()
		);
		for (const auto &device : participant->getPrivate()->getDevices())
386
			insertChatRoomParticipantDevice(participantId, insertSipAddress(device->getAddress().asString()), device->getName());
387 388
	}

389
	return chatRoomId;
390
}
391

392 393 394 395 396
long long MainDbPrivate::insertChatRoomParticipant (
	long long chatRoomId,
	long long participantSipAddressId,
	bool isAdmin
) {
397
	soci::session *session = dbSession.getBackendSession();
398 399
	long long chatRoomParticipantId = selectChatRoomParticipantId(chatRoomId, participantSipAddressId);
	if (chatRoomParticipantId >= 0) {
400
		// See: https://stackoverflow.com/a/15299655 (cast to reference)
401 402 403
		*session << "UPDATE chat_room_participant SET is_admin = :isAdmin WHERE id = :chatRoomParticipantId",
			soci::use(static_cast<const int &>(isAdmin)), soci::use(chatRoomParticipantId);
		return chatRoomParticipantId;
404
	}
405

406 407 408
	*session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)"
		" VALUES (:chatRoomId, :participantSipAddressId, :isAdmin)",
		soci::use(chatRoomId), soci::use(participantSipAddressId), soci::use(static_cast<const int &>(isAdmin));
409

410
	return dbSession.getLastInsertId();
411
}
412

413 414
void MainDbPrivate::insertChatRoomParticipantDevice (
	long long participantId,
415 416
	long long participantDeviceSipAddressId,
	const string &deviceName
417
) {
418
	soci::session *session = dbSession.getBackendSession();
419 420 421 422 423 424 425 426
	long long count;
	*session << "SELECT COUNT(*) FROM chat_room_participant_device"
		" WHERE chat_room_participant_id = :participantId"
		" AND participant_device_sip_address_id = :participantDeviceSipAddressId",
		soci::into(count), soci::use(participantId), soci::use(participantDeviceSipAddressId);
	if (count)
		return;

427 428 429
	*session << "INSERT INTO chat_room_participant_device (chat_room_participant_id, participant_device_sip_address_id, name)"
		" VALUES (:participantId, :participantDeviceSipAddressId, :participantDeviceName)",
		soci::use(participantId), soci::use(participantDeviceSipAddressId), soci::use(deviceName);
430
}
431

432 433 434 435 436 437
void MainDbPrivate::insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state, time_t stateChangeTime) {
	const tm &stateChangeTm = Utils::getTimeTAsTm(stateChangeTime);
	*dbSession.getBackendSession() <<
		"INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state, state_change_time)"
		" VALUES (:chatMessageId, :sipAddressId, :state, :stateChangeTm)",
		soci::use(chatMessageId), soci::use(sipAddressId), soci::use(state), soci::use(stateChangeTm);
438
}
439

440 441
// -----------------------------------------------------------------------------

442
long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const {
443
	long long sipAddressId;
444

445
	soci::session *session = dbSession.getBackendSession();
446
	*session << Statements::get(Statements::SelectSipAddressId),
447
		soci::use(sipAddress), soci::into(sipAddressId);
448

449
	return session->got_data() ? sipAddressId : -1;
450
}
451

452
long long MainDbPrivate::selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const {
453
	long long chatRoomId;
454

455
	soci::session *session = dbSession.getBackendSession();
456
	*session << Statements::get(Statements::SelectChatRoomId),
457
		soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::into(chatRoomId);
458

459
	return session->got_data() ? chatRoomId : -1;
460
}
461

462 463
long long MainDbPrivate::selectChatRoomId (const ConferenceId &conferenceId) const {
	long long peerSipAddressId = selectSipAddressId(conferenceId.getPeerAddress().asString());
464 465
	if (peerSipAddressId < 0)
		return -1;
466

467
	long long localSipAddressId = selectSipAddressId(conferenceId.getLocalAddress().asString());
468 469
	if (localSipAddressId < 0)
		return -1;
470

471 472
	return selectChatRoomId(peerSipAddressId, localSipAddressId);
}
473

474
long long MainDbPrivate::selectChatRoomParticipantId (long long chatRoomId, long long participantSipAddressId) const {
475
	long long chatRoomParticipantId;
476

477
	soci::session *session = dbSession.getBackendSession();
478
	*session << Statements::get(Statements::SelectChatRoomParticipantId),
479
		soci::use(chatRoomId), soci::use(participantSipAddressId), soci::into(chatRoomParticipantId);
480

481
	return session->got_data() ? chatRoomParticipantId : -1;
482
}
483

484
long long MainDbPrivate::selectOneToOneChatRoomId (long long sipAddressIdA, long long sipAddressIdB, bool encrypted) const {
485
	long long chatRoomId;
486 487
	const int encryptedCapability = int(ChatRoom::Capabilities::Encrypted);
	const int expectedCapabilities = encrypted ? encryptedCapability : 0;
488

489
	soci::session *session = dbSession.getBackendSession();
490
	*session << Statements::get(Statements::SelectOneToOneChatRoomId),
491
		soci::use(sipAddressIdA, "1"), soci::use(sipAddressIdB, "2"),
492
		soci::use(encryptedCapability, "3"), soci::use(expectedCapabilities, "4"),
493
		soci::into(chatRoomId);
494

495
	return session->got_data() ? chatRoomId : -1;
496 497
}

498 499
// -----------------------------------------------------------------------------

500 501 502
void MainDbPrivate::deleteContents (long long chatMessageId) {
	*dbSession.getBackendSession() << "DELETE FROM chat_message_content WHERE event_id = :chatMessageId",
		soci::use(chatMessageId);
503
}
504

505
void MainDbPrivate::deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId) {
506 507
	*dbSession.getBackendSession() << "DELETE FROM chat_room_participant"
		" WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId",
508 509
		soci::use(chatRoomId), soci::use(participantSipAddressId);
}
510

511 512 513 514
void MainDbPrivate::deleteChatRoomParticipantDevice (
	long long participantId,
	long long participantDeviceSipAddressId
) {
515 516 517
	*dbSession.getBackendSession() << "DELETE FROM chat_room_participant_device"
		" WHERE chat_room_participant_id = :participantId"
		" AND participant_device_sip_address_id = :participantDeviceSipAddressId",
518 519
		soci::use(participantId), soci::use(participantDeviceSipAddressId);
}
520

521 522
// -----------------------------------------------------------------------------
// Events API.
Ronan's avatar
Ronan committed
523 524
// -----------------------------------------------------------------------------

525
shared_ptr<EventLog> MainDbPrivate::selectGenericConferenceEvent (
526 527 528
	const shared_ptr<AbstractChatRoom> &chatRoom,
	const soci::row &row
) const {
529
	L_ASSERT(chatRoom);
530 531 532 533 534 535 536 537 538 539 540 541
	EventLog::Type type = EventLog::Type(row.get<int>(1));
	if (type == EventLog::Type::ConferenceChatMessage) {
		long long eventId = getConferenceEventIdFromRow(row);
		shared_ptr<EventLog> eventLog = getEventFromCache(eventId);
		if (!eventLog) {
			eventLog = selectConferenceChatMessageEvent(chatRoom, type, row);
			if (eventLog)
				cache(eventLog, eventId);
		}
		return eventLog;
	}

542
	return selectConferenceInfoEvent(chatRoom->getConferenceId(), row);
543 544
}

545
shared_ptr<EventLog> MainDbPrivate::selectConferenceInfoEvent (
546
	const ConferenceId &conferenceId,
547
	const soci::row &row
548
) const {
549 550 551 552
	long long eventId = getConferenceEventIdFromRow(row);
	shared_ptr<EventLog> eventLog = getEventFromCache(eventId);
	if (eventLog)
		return eventLog;
553

554
	EventLog::Type type = EventLog::Type(row.get<int>(1));
555 556
	switch (type) {
		case EventLog::Type::None:
557
		case EventLog::Type::ConferenceChatMessage:
558
			return nullptr;
559

560 561
		case EventLog::Type::ConferenceCreated:
		case EventLog::Type::ConferenceTerminated:
562
			eventLog = selectConferenceEvent(conferenceId, type, row);
563
			break;
Ronan's avatar
Ronan committed
564

565 566
		case EventLog::Type::ConferenceCallStart:
		case EventLog::Type::ConferenceCallEnd:
567
			eventLog = selectConferenceCallEvent(conferenceId, type, row);
568
			break;
Ronan's avatar
Ronan committed
569

570 571 572 573
		case EventLog::Type::ConferenceParticipantAdded:
		case EventLog::Type::ConferenceParticipantRemoved:
		case EventLog::Type::ConferenceParticipantSetAdmin:
		case EventLog::Type::ConferenceParticipantUnsetAdmin:
574
			eventLog = selectConferenceParticipantEvent(conferenceId, type, row);
575
			break;
Ronan's avatar
Ronan committed
576

577 578
		case EventLog::Type::ConferenceParticipantDeviceAdded:
		case EventLog::Type::ConferenceParticipantDeviceRemoved:
579
			eventLog = selectConferenceParticipantDeviceEvent(conferenceId, type, row);
580
			break;
581

582
		case EventLog::Type::ConferenceSubjectChanged:
583
			eventLog = selectConferenceSubjectEvent(conferenceId, type, row);
584
			break;
585 586 587 588

		case EventLog::Type::ConferenceSecurityEvent:
			eventLog = selectConferenceSecurityEvent(conferenceId, type, row);
			break;
589 590
	}

591 592
	if (eventLog)
		cache(eventLog, eventId);
Ronan's avatar
Ronan committed
593

594 595
	return eventLog;
}
Ronan's avatar
Ronan committed
596

597
shared_ptr<EventLog> MainDbPrivate::selectConferenceEvent (
598
	const ConferenceId &conferenceId,
599
	EventLog::Type type,
600
	const soci::row &row
601 602 603
) const {
	return make_shared<ConferenceEvent>(
		type,
604
		getConferenceEventCreationTimeFromRow(row),
605
		conferenceId
606 607
	);
}
608

609
shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
610
	const ConferenceId &conferenceId,
611
	EventLog::Type type,
612
	const soci::row &row
613 614 615 616
) const {
	// TODO.
	return nullptr;
}
Ronan's avatar
Ronan committed
617

618
shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
619
	const shared_ptr<AbstractChatRoom> &chatRoom,
620
	EventLog::Type type,
621
	const soci::row &row
622
) const {
623
	long long eventId = getConferenceEventIdFromRow(row);
624 625
	shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
	if (!chatMessage) {
626 627
		chatMessage = shared_ptr<ChatMessage>(new ChatMessage(
			chatRoom,
628
			ChatMessage::Direction(row.get<int>(8))
629
		));
630
		chatMessage->setIsSecured(!!row.get<int>(9));
Ronan's avatar
Ronan committed
631

632
		ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
633
		ChatMessage::State messageState = ChatMessage::State(row.get<int>(7));
634 635 636
		// This is necessary if linphone has crashed while sending a message. It will set the correct state so the user can resend it.
		if (messageState == ChatMessage::State::Idle || messageState == ChatMessage::State::InProgress)
			messageState = ChatMessage::State::NotDelivered;
637
		dChatMessage->forceState(messageState);
638

639 640
		dChatMessage->forceFromAddress(IdentityAddress(row.get<string>(3)));
		dChatMessage->forceToAddress(IdentityAddress(row.get<string>(4)));
Ronan's avatar
Ronan committed
641

642
		dChatMessage->setTime(dbSession.getTime(row, 5));
643
		dChatMessage->setImdnMessageId(row.get<string>(6));
644 645
		dChatMessage->setPositiveDeliveryNotificationRequired(!!row.get<int>(14));
		dChatMessage->setDisplayNotificationRequired(!!row.get<int>(15));
646

647 648
		dChatMessage->markContentsAsNotLoaded();
		dChatMessage->setIsReadOnly(true);
649

650
		cache(chatMessage, eventId);
Ronan's avatar
Ronan committed
651 652
	}

653
	return make_shared<ConferenceChatMessageEvent>(
654
		getConferenceEventCreationTimeFromRow(row),
655 656 657
		chatMessage
	);
}
658

659
shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
660
	const ConferenceId &conferenceId,
661
	EventLog::Type type,
662
	const soci::row &row
663 664 665
) const {
	return make_shared<ConferenceParticipantEvent>(
		type,
666
		getConferenceEventCreationTimeFromRow(row),
667
		conferenceId,
668 669
		getConferenceEventNotifyIdFromRow(row),
		IdentityAddress(row.get<string>(12))
670 671
	);
}
672

673
shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantDeviceEvent (
674
	const ConferenceId &conferenceId,
675
	EventLog::Type type,
676
	const soci::row &row
677 678 679
) const {
	return make_shared<ConferenceParticipantDeviceEvent>(
		type,
680
		getConferenceEventCreationTimeFromRow(row),
681
		conferenceId,
682 683 684
		getConferenceEventNotifyIdFromRow(row),
		IdentityAddress(row.get<string>(12)),
		IdentityAddress(row.get<string>(11))
685 686
	);
}
687

688 689 690 691 692 693 694 695 696 697 698 699 700
shared_ptr<EventLog> MainDbPrivate::selectConferenceSecurityEvent (
	const ConferenceId &conferenceId,
	EventLog::Type type,
	const soci::row &row
) const {
	return make_shared<ConferenceSecurityEvent>(
		getConferenceEventCreationTimeFromRow(row),
		conferenceId,
		static_cast<ConferenceSecurityEvent::SecurityEventType>(row.get<int>(16)),
		IdentityAddress(row.get<string>(17))
	);
}

701
shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
702
	const ConferenceId &conferenceId,
703
	EventLog::Type type,
704
	const soci::row &row
705 706
) const {
	return make_shared<ConferenceSubjectEvent>(
707
		getConferenceEventCreationTimeFromRow(row),
708
		conferenceId,
709 710
		getConferenceEventNotifyIdFromRow(row),
		row.get<string>(13)
711 712
	);
}
713

714
// -----------------------------------------------------------------------------
715

716
long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
717
	const int &type = int(eventLog->getType());
718
	const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
719 720
	*dbSession.getBackendSession() << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)",
		soci::use(type), soci::use(creationTime);
721

722
	return dbSession.getLastInsertId();
723
}
724

725 726 727 728
long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
	shared_ptr<ConferenceEvent> conferenceEvent = static_pointer_cast<ConferenceEvent>(eventLog);

	long long eventId = -1;
729 730
	const ConferenceId &conferenceId = conferenceEvent->getConferenceId();
	const long long &curChatRoomId = selectChatRoomId(conferenceId);
731 732 733
	if (curChatRoomId < 0) {
		// A conference event can be inserted in database only if chat room exists.
		// Otherwise it's an error.
734
		lError() << "Unable to find chat room storage id of: " << conferenceId << ".";
735 736
	} else {
		eventId = insertEvent(eventLog);
737

738
		soci::session *session = dbSession.getBackendSession();
739 740 741 742 743 744 745
		*session << "INSERT INTO conference_event (event_id, chat_room_id)"
			" VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(curChatRoomId);

		const tm &lastUpdateTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
		*session << "UPDATE chat_room SET last_update_time = :lastUpdateTime"
			" WHERE id = :chatRoomId", soci::use(lastUpdateTime),
			soci::use(curChatRoomId);
746

747
		if (eventLog->getType() == EventLog::Type::ConferenceTerminated)
748
			*session << "UPDATE chat_room SET flags = 1, last_notify_id = 0 WHERE id = :chatRoomId", soci::use(curChatRoomId);
749 750
		else if (eventLog->getType() == EventLog::Type::ConferenceCreated)
			*session << "UPDATE chat_room SET flags = 0 WHERE id = :chatRoomId", soci::use(curChatRoomId);
751 752
	}

753 754 755 756 757
	if (chatRoomId)
		*chatRoomId = curChatRoomId;

	return eventId;
}
758

759 760 761 762
long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
	// TODO.
	return 0;
}
763

764 765 766 767 768
long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
	const long long &eventId = insertConferenceEvent(eventLog);
	if (eventId < 0)
		return -1;

769
	shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
770 771 772
	const long long &fromSipAddressId = insertSipAddress(chatMessage->getFromAddress().asString());
	const long long &toSipAddressId = insertSipAddress(chatMessage->getToAddress().asString());
	const tm &messageTime = Utils::getTimeTAsTm(chatMessage->getTime());
773 774
	const int &state = int(chatMessage->getState());
	const int &direction = int(chatMessage->getDirection());
775 776
	const string &imdnMessageId = chatMessage->getImdnMessageId();
	const int &isSecured = chatMessage->isSecured() ? 1 : 0;
777 778
	const int &deliveryNotificationRequired = chatMessage->getPrivate()->getPositiveDeliveryNotificationRequired();
	const int &displayNotificationRequired = chatMessage->getPrivate()->getDisplayNotificationRequired();
779

780
	*dbSession.getBackendSession() << "INSERT INTO conference_chat_message_event ("
781
		"  event_id, from_sip_address_id, to_sip_address_id,"
782 783
		"  time, state, direction, imdn_message_id, is_secured,"
		"  delivery_notification_required, display_notification_required"
784 785
		") VALUES ("
		"  :eventId, :localSipaddressId, :remoteSipaddressId,"
786 787
		"  :time, :state, :direction, :imdnMessageId, :isSecured,"
		"  :deliveryNotificationRequired, :displayNotificationRequired"
788 789
		")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId),
		soci::use(messageTime), soci::use(state), soci::use(direction),
790 791
		soci::use(imdnMessageId), soci::use(isSecured),
		soci::use(deliveryNotificationRequired), soci::use(displayNotificationRequired);
792 793 794 795

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

796 797
	shared_ptr<AbstractChatRoom> chatRoom(chatMessage->getChatRoom());
	for (const auto &participant : chatRoom->getParticipants()) {
798
		const long long &participantSipAddressId = selectSipAddressId(participant->getAddress().asString());
799
		insertChatMessageParticipant(eventId, participantSipAddressId, state, chatMessage->getTime());
800 801
	}

802
	if (direction == int(ChatMessage::Direction::Incoming) && state != int(ChatMessage::State::Displayed)) {
803
		int *count = unreadChatMessageCountCache[chatRoom->getConferenceId()];
804 805 806 807
		if (count)
			++*count;
	}

808 809 810 811 812
	return eventId;
}

void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
	shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
813

814 815 816
	const EventLogPrivate *dEventLog = eventLog->getPrivate();
	MainDbKeyPrivate *dEventKey = static_cast<MainDbKey &>(dEventLog->dbKey).getPrivate();
	const long long &eventId = dEventKey->storageId;
817

818
	// 1. Get current chat message state and database state.
819
	const ChatMessage::State state = chatMessage->getState();
820 821 822 823 824 825 826
	ChatMessage::State dbState;
	{
		int intState;
		*dbSession.getBackendSession() << "SELECT state FROM conference_chat_message_event WHERE event_id = :eventId",
			soci::into(intState), soci::use(eventId);
		dbState = ChatMessage::State(intState);
	}
827

828 829
	// 2. Update unread chat message count if necessary.
	const bool isOutgoing = chatMessage->getDirection() == ChatMessage::Direction::Outgoing;
830
	shared_ptr<AbstractChatRoom> chatRoom(chatMessage->getChatRoom());
831
	if (!isOutgoing && state == ChatMessage::State::Displayed) {
832
		int *count = unreadChatMessageCountCache[chatRoom->getConferenceId()];
833 834 835
		if (count && dbState != ChatMessage::State::Displayed) {
			L_ASSERT(*count > 0);
			--*count;
836 837 838
		}
	}

839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
	// 3. Update chat message event.
	{
		const string &imdnMessageId = chatMessage->getImdnMessageId();
		// Do not store transfer state.
		const int stateInt = int(
			state == ChatMessage::State::InProgress ||
			state == ChatMessage::State::FileTransferDone ||
			state == ChatMessage::State::FileTransferError
				? dbState
				: state
		);

		*dbSession.getBackendSession() << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = :imdnMessageId"
			" WHERE event_id = :eventId",
			soci::use(stateInt), soci::use(imdnMessageId), soci::use(eventId);
	}
855

856
	// 4. Update contents.
857 858 859
	deleteContents(eventId);
	for (const auto &content : chatMessage->getContents())
		insertContent(eventId, *content);
860

861
	// 5. Update participants.
862
	if (isOutgoing && (state == ChatMessage::State::Delivered || state == ChatMessage::State::NotDelivered))
863
		for (const auto &participant : chatRoom->getParticipants())
864
			setChatMessageParticipantState(eventLog, participant->getAddress(), state, std::time(nullptr));
865
}
866

867 868 869 870 871
long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
	long long curChatRoomId;
	const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId);
	if (eventId < 0)
		return -1;
872

873
	const unsigned int &lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
874

875
	soci::session *session = dbSession.getBackendSession();
876 877 878 879
	*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
		" VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId);
	*session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId",
		soci::use(lastNotifyId), soci::use(curChatRoomId);
880

881 882
	if (chatRoomId)
		*chatRoomId = curChatRoomId;
883

884 885
	return eventId;
}
886

887 888 889 890 891 892 893 894
long long MainDbPrivate::insertConferenceParticipantEvent (
	const shared_ptr<EventLog> &eventLog,
	long long *chatRoomId
) {
	long long curChatRoomId;
	const long long &eventId = insertConferenceNotifiedEvent(eventLog, &curChatRoomId);
	if (eventId < 0)
		return -1;
895

896 897
	shared_ptr<ConferenceParticipantEvent> participantEvent =
		static_pointer_cast<ConferenceParticipantEvent>(eventLog);
898

899 900 901 902
	const long long &participantAddressId = insertSipAddress(
		participantEvent->getParticipantAddress().asString()
	);

903 904
	*dbSession.getBackendSession() << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)"
		" VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919

	bool isAdmin = eventLog->getType() == EventLog::Type::ConferenceParticipantSetAdmin;
	switch (eventLog->getType()) {
		case EventLog::Type::ConferenceParticipantAdded:
		case EventLog::Type::ConferenceParticipantSetAdmin:
		case EventLog::Type::ConferenceParticipantUnsetAdmin:
			insertChatRoomParticipant(curChatRoomId, participantAddressId, isAdmin);
			break;

		case EventLog::Type::ConferenceParticipantRemoved:
			deleteChatRoomParticipant(curChatRoomId, participantAddressId);
			break;

		default:
			break;
920 921
	}

922 923
	if (chatRoomId)
		*chatRoomId = curChatRoomId;
924

925 926
	return eventId;
}
927

928 929 930 931 932
long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
	long long chatRoomId;
	const long long &eventId = insertConferenceParticipantEvent(eventLog, &chatRoomId);
	if (eventId < 0)
		return -1;
933

934 935
	shared_ptr<ConferenceParticipantDeviceEvent> participantDeviceEvent =
		static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog);
936

937 938 939 940 941 942 943 944 945
	const string participantAddress = participantDeviceEvent->getParticipantAddress().asString();
	const long long &participantAddressId = selectSipAddressId(participantAddress);
	if (participantAddressId < 0) {
		lError() << "Unable to find sip address id of: `" << participantAddress << "`.";
		return -1;
	}
	const long long &participantId = selectChatRoomParticipantId(chatRoomId, participantAddressId);
	if (participantId < 0) {
		lError() << "Unable to find valid participant id in database with chat room id = " << chatRoomId <<
946
		" and participant address id = " << participantAddressId;
947 948 949 950 951
		return -1;
	}
	const long long &deviceAddressId = insertSipAddress(
		participantDeviceEvent->getDeviceAddress().asString()
	);
952

953
	*dbSession.getBackendSession() << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)"
954
		" VALUES (:eventId, :deviceAddressId)", soci::use(eventId), soci::use(deviceAddressId);
955

956 957
	switch (eventLog->getType()) {
		case EventLog::Type::ConferenceParticipantDeviceAdded:
958
			insertChatRoomParticipantDevice(participantId, deviceAddressId, participantDeviceEvent->getDeviceName());
959
			break;
960

961 962 963 964 965 966
		case EventLog::Type::ConferenceParticipantDeviceRemoved:
			deleteChatRoomParticipantDevice(participantId, deviceAddressId);
			break;

		default:
			break;
967 968
	}

969 970
	return eventId;
}
971

972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988
long long MainDbPrivate::insertConferenceSecurityEvent (const shared_ptr<EventLog> &eventLog) {
	long long chatRoomId;
	const long long &eventId = insertConferenceEvent(eventLog, &chatRoomId);
	if (eventId < 0)
		return -1;

	const int &securityEventType = int(static_pointer_cast<ConferenceSecurityEvent>(eventLog)->getSecurityEventType());
	const string &faultyDevice = static_pointer_cast<ConferenceSecurityEvent>(eventLog)->getFaultyDeviceAddress().asString();

	// insert security event into new table "conference_security_event"
	soci::session *session = dbSession.getBackendSession();
	*session << "INSERT INTO conference_security_event (event_id, security_alert, faulty_device)"
		" VALUES (:eventId, :securityEventType, :faultyDevice)", soci::use(eventId), soci::use(securityEventType), soci::use(faultyDevice);

	return eventId;
}

989 990 991 992 993
long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
	long long chatRoomId;
	const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
	if (eventId < 0)
		return -1;
994

995
	const string &subject = static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject();
996

997
	soci::session *session = dbSession.getBackendSession();
998 999
	*session << "INSERT INTO conference_subject_event (event_id, subject)"
		" VALUES (:eventId, :subject)", soci::use(eventId), soci::use(subject);
1000

1001 1002 1003 1004 1005
	*session << "UPDATE chat_room SET subject = :subject"
		" WHERE id = :chatRoomId", soci::use(subject), soci::use(chatRoomId);

	return eventId;
}
1006

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
void MainDbPrivate::setChatMessageParticipantState (
	const shared_ptr<EventLog> &eventLog,
	const IdentityAddress &participantAddress,
	ChatMessage::State state,
	time_t stateChangeTime
) {
	const EventLogPrivate *dEventLog = eventLog->getPrivate();
	MainDbKeyPrivate *dEventKey = static_cast<MainDbKey &>(dEventLog->dbKey).getPrivate();
	const long long &eventId = dEventKey->storageId;
	const long long &participantSipAddressId = selectSipAddressId(participantAddress.asString());
1017
	int stateInt = int(state);
1018 1019 1020 1021 1022 1023 1024 1025
	const tm &stateChangeTm = Utils::getTimeTAsTm(stateChangeTime);

	*dbSession.getBackendSession() << "UPDATE chat_message_participant SET state = :state,"
		" state_change_time = :stateChangeTm"
		" WHERE event_id = :eventId AND participant_sip_address_id = :participantSipAddressId",
		soci::use(stateInt), soci::use(stateChangeTm), soci::use(eventId), soci::use(participantSipAddressId);
}

1026 1027
// -----------------------------------------------------------------------------
// Cache API.
1028 1029
// -----------------------------------------------------------------------------

1030 1031 1032 1033
shared_ptr<EventLog> MainDbPrivate::getEventFromCache (long long storageId) const {
	auto it = storageIdToEvent.find(storageId);
	if (it == storageIdToEvent.cend())
		return nullptr;
1034

1035 1036 1037 1038
	shared_ptr<EventLog> eventLog = it->second.lock();
	L_ASSERT(eventLog);
	return eventLog;
}
1039

1040 1041 1042 1043
shared_ptr<ChatMessage> MainDbPrivate::getChatMessageFromCache (long long storageId) const {
	auto it = storageIdToChatMessage.find(storageId);
	if (it == storageIdToChatMessage.cend())
		return nullptr;
1044

1045 1046 1047 1048
	shared_ptr<ChatMessage> chatMessage = it->second.lock();
	L_ASSERT(chatMessage);
	return chatMessage;
}
1049

1050 1051
void MainDbPrivate::cache (const shared_ptr<EventLog> &eventLog, long long storageId) const {
	L_Q();
1052

1053 1054 1055 1056 1057 1058
	EventLogPrivate *dEventLog = eventLog->getPrivate();
	L_ASSERT(!dEventLog->dbKey.isValid());
	dEventLog->dbKey = MainDbEventKey(q->getCore(), storageId);
	storageIdToEvent[storageId] = eventLog;
	L_ASSERT(dEventLog->dbKey.isValid());
}
1059

1060 1061
void MainDbPrivate::cache (const shared_ptr<ChatMessage> &chatMessage, long long storageId) const {
	L_Q();
1062

1063 1064 1065 1066 1067 1068
	ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
	L_ASSERT(!dChatMessage->dbKey.isValid());
	dChatMessage->dbKey = MainDbChatMessageKey(q->getCore(), storageId);
	storageIdToChatMessage[storageId] = chatMessage;
	L_ASSERT(dChatMessage->dbKey.isValid());
}
1069

1070
void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) {
1071
	soci::rowset<soci::row> rows = (dbSession.getBackendSession()->prepare << query, soci::use(chatRoomId));
1072
	for (const auto &row : rows) {
1073
		long long eventId = dbSession.resolveId(row, 0);
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
		shared_ptr<EventLog> eventLog = getEventFromCache(eventId);
		if (eventLog) {
			const EventLogPrivate *dEventLog = eventLog->getPrivate();
			L_ASSERT(dEventLog->dbKey.isValid());
			dEventLog->dbKey = MainDbEventKey();
		}
		shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
		if (chatMessage) {
			const ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
			L_ASSERT(dChatMessage->dbKey.isValid());
			dChatMessage->dbKey = MainDbChatMessageKey();
1085 1086
		}
	}
1087
}
1088

1089 1090
// -----------------------------------------------------------------------------
// Versions.
1091 1092
// -----------------------------------------------------------------------------

1093
unsigned int MainDbPrivate::getModuleVersion (const string &name) {
1094
	soci::session *session = dbSession.getBackendSession();
1095

1096 1097 1098 1099
	unsigned int version;
	*session << "SELECT version FROM db_module_version WHERE name = :name", soci::into(version), soci::use(name);
	return session->got_data() ? version : 0;
}
1100

1101 1102
void MainDbPrivate::updateModuleVersion (const string &name, unsigned int version) {
	unsigned int oldVersion = getModuleVersion(name);
1103
	if (version <= oldVersion)
1104
		return;
1105

1106
	soci::session *session = dbSession.getBackendSession();
1107 1108 1109
	*session << "REPLACE INTO db_module_version (name, version) VALUES (:name, :version)",
		soci::