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

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

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

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

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

Ronan's avatar
Ronan committed
53
54
55
56
LINPHONE_BEGIN_NAMESPACE

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

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

59
60
#ifdef SOCI_ENABLED

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

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

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

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

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

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

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

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

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

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

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

123
124
125
		return sql;
	}

126
127
// -----------------------------------------------------------------------------

128
129
130
131
132
133
	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))
Benjamin REIS's avatar
Benjamin REIS committed
134
			: static_cast<long long>(row.get<unsigned long long>(0));
135
136
	}

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

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

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

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

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

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

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

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

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

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

190
191
192
193
	void MainDbPrivate::removeContentsForChatMessageEvent (long long eventId) {
		soci::session *session = dbSession.getBackendSession<soci::session>();

		*session << "DELETE FROM chat_message_content WHERE event_id=:eventId", soci::use(eventId);
Benjamin REIS's avatar
Benjamin REIS committed
194

195
196
197
198
199
200
201
		//TODO: remove file content if exists
		//*session << "DELETE FROM chat_message_file_content WHERE chat_message_content_id=:messageContentId", soci::use(messageContentId);

		//TODO: remove contents' app_data
		//*session << "DELETE FROM chat_message_content_app_data WHERE chat_message_content_id=:messageContentId", soci::use(messageContentId);
	}

202
	long long MainDbPrivate::insertContentType (const string &contentType) {
203
204
205
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

206
		long long id;
207
208
		*session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(id);
		if (session->got_data())
209
210
			return id;

Ronan's avatar
Ronan committed
211
		lInfo() << "Insert new content type in database: `" << contentType << "`.";
212
213
		*session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType);
		return q->getLastInsertId();
214
215
	}

216
	long long MainDbPrivate::insertBasicChatRoom (
217
218
		long long peerSipAddressId,
		long long localSipAddressId,
219
		const tm &creationTime
220
221
222
	) {
		L_Q();

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

225
		long long id = selectChatRoomId(peerSipAddressId, localSipAddressId);
226
227
		if (id >= 0)
			return id;
228

229
		static const int capabilities = static_cast<int>(ChatRoom::Capabilities::Basic);
230
231
232
233
234
235
236
237
238
		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();
239
240
	}

241
	long long MainDbPrivate::insertChatRoom (const shared_ptr<ChatRoom> &chatRoom) {
242
243
244
245
246
		L_Q();

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

		const ChatRoomId &chatRoomId = chatRoom->getChatRoomId();
247
248
		const long long &peerSipAddressId = insertSipAddress(chatRoomId.getPeerAddress().asString());
		const long long &localSipAddressId = insertSipAddress(chatRoomId.getLocalAddress().asString());
249
250
251
252
253
254
255
256
257
258
259

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

260
261
262
		const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime());
		const int &capabilities = static_cast<int>(chatRoom->getCapabilities());
		const string &subject = chatRoom->getSubject();
263
		const int &flags = chatRoom->hasBeenLeft();
264
		*session << "INSERT INTO chat_room ("
265
			"  peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities, subject, flags"
266
			") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities, :subject, :flags)",
267
			soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime),
268
			soci::use(capabilities), soci::use(subject), soci::use(flags);
269
270

		id = q->getLastInsertId();
271
272
273
274

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

275
276
277
		// Do not add 'me' when creating a server-group-chat-room.
		if (chatRoomId.getLocalAddress() != chatRoomId.getPeerAddress()) {
			shared_ptr<Participant> me = chatRoom->getMe();
278
279
280
281
282
283
284
			long long meId = insertChatRoomParticipant(
				id,
				insertSipAddress(me->getAddress().asString()),
				me->isAdmin()
			);
			for (const auto &device : me->getPrivate()->getDevices())
				insertChatRoomParticipantDevice(meId, insertSipAddress(device->getAddress().asString()));
285
286
		}

287
288
289
290
291
292
293
294
295
		for (const auto &participant : chatRoom->getParticipants()) {
			long long participantId = insertChatRoomParticipant(
				id,
				insertSipAddress(participant->getAddress().asString()),
				participant->isAdmin()
			);
			for (const auto &device : participant->getPrivate()->getDevices())
				insertChatRoomParticipantDevice(participantId, insertSipAddress(device->getAddress().asString()));
		}
296
297

		return id;
298
299
	}

300
301
	long long MainDbPrivate::insertChatRoomParticipant (long long chatRoomId, long long sipAddressId, bool isAdmin) {
		L_Q();
302

303
		long long id = -1;
304
		soci::session *session = dbSession.getBackendSession<soci::session>();
305
306
307
308
309
310
311
312
313
314
		*session << "SELECT id from chat_room_participant"
			"  WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :sipAddressId",
			soci::into(id), soci::use(chatRoomId), soci::use(sipAddressId);

		if (id >= 0) {
			// See: https://stackoverflow.com/a/15299655 (cast to reference)
			*session << "UPDATE chat_room_participant SET is_admin = :isAdmin"
				"  WHERE id = :id",
				soci::use(static_cast<const int &>(isAdmin)), soci::use(id);
			return id;
Ronan's avatar
Ronan committed
315
		}
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

		lInfo() << "Insert new chat room participant in database: `" << sipAddressId << "` (isAdmin=" << isAdmin << ").";
		*session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)"
			"  VALUES (:chatRoomId, :sipAddressId, :isAdmin)",
			soci::use(chatRoomId), soci::use(sipAddressId), soci::use(static_cast<const int &>(isAdmin));

		return q->getLastInsertId();
	}

	void MainDbPrivate::insertChatRoomParticipantDevice (long long participantId, long long sipAddressId) {
		soci::session *session = dbSession.getBackendSession<soci::session>();
		long long count;
		*session << "SELECT COUNT(*) FROM chat_room_participant_device"
			"  WHERE chat_room_participant_id = :participantId"
			"  AND participant_device_sip_address_id = :sipAddressId",
			soci::into(count), soci::use(participantId), soci::use(sipAddressId);
		if (count)
			return;

		lInfo() << "Insert new chat room participant device in database: `" << sipAddressId << "`.";
		*session << "INSERT INTO chat_room_participant_device (chat_room_participant_id, participant_device_sip_address_id)"
			"  VALUES (:participantId, :sipAddressId)",
			soci::use(participantId), soci::use(sipAddressId);
339
340
	}

341
	void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) {
342
343
		soci::session *session = dbSession.getBackendSession<soci::session>();
		soci::statement statement = (
344
			session->prepare << "UPDATE chat_message_participant SET state = :state"
345
				"  WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId",
Ronan's avatar
Ronan committed
346
				soci::use(state), soci::use(eventId), soci::use(sipAddressId)
347
348
		);
		statement.execute(true);
349
		if (statement.get_affected_rows() == 0 && state != static_cast<int>(ChatMessage::State::Displayed))
350
			*session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)"
Ronan's avatar
Ronan committed
351
352
				"  VALUES (:eventId, :sipAddressId, :state)",
				soci::use(eventId), soci::use(sipAddressId), soci::use(state);
353
354
	}

355
	long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const {
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
		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);
	}

385
386
387
388
389
390
391
	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
392
393
// -----------------------------------------------------------------------------

394
	shared_ptr<EventLog> MainDbPrivate::selectGenericConferenceEvent (
395
		long long eventId,
396
		EventLog::Type type,
397
		time_t creationTime,
398
		const ChatRoomId &chatRoomId
399
	) const {
Ronan's avatar
Ronan committed
400
401
		shared_ptr<EventLog> eventLog;

Ronan's avatar
Ronan committed
402
403
404
405
406
		switch (type) {
			case EventLog::Type::None:
				return nullptr;

			case EventLog::Type::ConferenceCreated:
407
			case EventLog::Type::ConferenceTerminated:
Ronan's avatar
Ronan committed
408
409
				eventLog = selectConferenceEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
410

411
412
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
Ronan's avatar
Ronan committed
413
414
				eventLog = selectConferenceCallEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
415
416

			case EventLog::Type::ConferenceChatMessage:
Ronan's avatar
Ronan committed
417
418
				eventLog = selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
419
420
421
422
423

			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
Ronan's avatar
Ronan committed
424
425
				eventLog = selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
426
427
428

			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
Ronan's avatar
Ronan committed
429
430
				eventLog = selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
431
432

			case EventLog::Type::ConferenceSubjectChanged:
Ronan's avatar
Ronan committed
433
434
435
436
				eventLog = selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId);
				break;
		}

437
438
		if (eventLog)
			cache(eventLog, eventId);
Ronan's avatar
Ronan committed
439

440
		return eventLog;
Ronan's avatar
Ronan committed
441
442
	}

443
	shared_ptr<EventLog> MainDbPrivate::selectConferenceEvent (
444
		long long,
445
		EventLog::Type type,
446
		time_t creationTime,
447
		const ChatRoomId &chatRoomId
448
	) const {
449
450
		return make_shared<ConferenceEvent>(
			type,
451
			creationTime,
452
			chatRoomId
453
		);
Ronan's avatar
Ronan committed
454
455
	}

456
	shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
457
		long long eventId,
458
		EventLog::Type type,
459
		time_t creationTime,
460
		const ChatRoomId &chatRoomId
461
	) const {
Ronan's avatar
Ronan committed
462
463
464
465
		// TODO.
		return nullptr;
	}

466
	shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
467
		long long eventId,
468
		EventLog::Type type,
469
		time_t creationTime,
470
		const ChatRoomId &chatRoomId
471
	) const {
Ronan's avatar
Ronan committed
472
473
474
		L_Q();

		shared_ptr<Core> core = q->getCore();
475
		shared_ptr<ChatRoom> chatRoom = core->findChatRoom(chatRoomId);
Ronan's avatar
Ronan committed
476
477
478
		if (!chatRoom)
			return nullptr;

479
480
		bool hasFileTransferContent = false;

481
		// 1 - Fetch chat message.
482
483
484
		shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
		if (chatMessage)
			goto end;
485
		{
486
487
			string fromSipAddress;
			string toSipAddress;
488
489
490

			tm messageTime;

491
			string imdnMessageId;
492

493
494
495
496
497
			int state;
			int direction;
			int isSecured;

			soci::session *session = dbSession.getBackendSession<soci::session>();
498
			*session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured"
499
500
				"  FROM event, conference_chat_message_event, sip_address AS from_sip_address,"
				"  sip_address AS to_sip_address"
501
502
				"  WHERE event_id = :eventId"
				"  AND event_id = event.id"
503
504
				"  AND from_sip_address_id = from_sip_address.id"
				"  AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress),
505
506
				soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction),
				soci::into(isSecured), soci::use(eventId);
507

508
509
510
511
			chatMessage = shared_ptr<ChatMessage>(new ChatMessage(
				chatRoom,
				static_cast<ChatMessage::Direction>(direction)
			));
512
513
			chatMessage->setIsSecured(static_cast<bool>(isSecured));

514
515
			ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
			dChatMessage->setState(static_cast<ChatMessage::State>(state), true);
516

517
518
519
520
			dChatMessage->forceFromAddress(IdentityAddress(fromSipAddress));
			dChatMessage->forceToAddress(IdentityAddress(toSipAddress));

			dChatMessage->setTime(Utils::getTmAsTimeT(messageTime));
521
		}
Ronan's avatar
Ronan committed
522

523
524
525
		// 2 - Fetch contents.
		{
			soci::session *session = dbSession.getBackendSession<soci::session>();
526
527
			static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body"
				"  FROM chat_message_content, content_type"
528
529
530
				"  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) {
531
				ContentType contentType(row.get<string>(2));
532
				const long long &contentId = resolveId(row, 0);
533
534
				Content *content;

535
536
				if (contentType == ContentType::FileTransfer) {
					hasFileTransferContent = true;
537
					content = new FileTransferContent();
538
				}
539
				else if (contentType.isFile()) {
540
					// 2.1 - Fetch contents' file informations.
541
542
543
544
545
					string name;
					int size;
					string path;

					*session << "SELECT name, size, path FROM chat_message_file_content"
Ronan's avatar
Ronan committed
546
						"  WHERE chat_message_content_id = :contentId",
Ronan's avatar
Ronan committed
547
						soci::into(name), soci::into(size), soci::into(path), soci::use(contentId);
548
549
550
551
552
553
554
555

					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
556
					content = new Content();
557

558
				// 2.2 - Fetch contents' app data.
Ronan's avatar
Ronan committed
559
560
561
562
563
				static const string query = "SELECT name, data FROM chat_message_content_app_data"
					"  WHERE chat_message_content_id = :contentId";
				soci::rowset<soci::row> rows = (session->prepare << query, soci::use(contentId));
				for (const auto &row : rows)
					content->setAppData(row.get<string>(0), row.get<string>(1));
564

565
				content->setContentType(contentType);
Sylvain Berfini's avatar
Sylvain Berfini committed
566
				content->setBody(row.get<string>(3));
567
				chatMessage->addContent(*content);
568
			}
Ronan's avatar
Ronan committed
569
		}
570

571
572
573
574
575
576
		// 3 - Load external body url from body into FileTransferContent if needed
		if (hasFileTransferContent) {
			ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
			dChatMessage->loadFileTransferUrlFromBodyToContent();
		}

577
578
		cache(chatMessage, eventId);

579
	end:
580
		return make_shared<ConferenceChatMessageEvent>(
581
			creationTime,
582
583
			chatMessage
		);
Ronan's avatar
Ronan committed
584
585
	}

586
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
587
		long long eventId,
588
		EventLog::Type type,
589
		time_t creationTime,
590
		const ChatRoomId &chatRoomId
591
	) const {
592
593
594
595
		unsigned int notifyId;
		string participantAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
596
597
		*session << "SELECT notify_id, participant_address.value"
			"  FROM conference_notified_event, conference_participant_event, sip_address as participant_address"
598
			"  WHERE conference_participant_event.event_id = :eventId"
Ronan's avatar
Ronan committed
599
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
600
			"    AND participant_address.id = participant_sip_address_id",
601
602
603
604
			soci::into(notifyId), soci::into(participantAddress), soci::use(eventId);

		return make_shared<ConferenceParticipantEvent>(
			type,
605
			creationTime,
606
			chatRoomId,
607
			notifyId,
608
			IdentityAddress(participantAddress)
609
		);
Ronan's avatar
Ronan committed
610
611
	}

612
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantDeviceEvent (
613
		long long eventId,
614
		EventLog::Type type,
615
		time_t creationTime,
616
		const ChatRoomId &chatRoomId
617
	) const {
618
619
		unsigned int notifyId;
		string participantAddress;
620
		string deviceAddress;
621
622

		soci::session *session = dbSession.getBackendSession<soci::session>();
623
		*session << "SELECT notify_id, participant_address.value, device_address.value"
624
			"  FROM conference_notified_event, conference_participant_event, conference_participant_device_event,"
625
			"    sip_address AS participant_address, sip_address AS device_address"
626
627
			"  WHERE conference_participant_device_event.event_id = :eventId"
			"    AND conference_participant_event.event_id = conference_participant_device_event.event_id"
628
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
629
630
			"    AND participant_address.id = participant_sip_address_id"
			"    AND device_address.id = device_sip_address_id",
631
			soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId);
632
633
634

		return make_shared<ConferenceParticipantDeviceEvent>(
			type,
635
			creationTime,
636
			chatRoomId,
637
			notifyId,
638
639
			IdentityAddress(participantAddress),
			IdentityAddress(deviceAddress)
640
		);
Ronan's avatar
Ronan committed
641
642
	}

643
	shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
644
		long long eventId,
645
		EventLog::Type type,
646
		time_t creationTime,
647
		const ChatRoomId &chatRoomId
648
649
650
651
	) const {
		unsigned int notifyId;
		string subject;

652
653
654
655
656
657
		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);
658
659

		return make_shared<ConferenceSubjectEvent>(
660
			creationTime,
661
			chatRoomId,
662
663
664
			notifyId,
			subject
		);
Ronan's avatar
Ronan committed
665
666
	}

667
668
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
669
	long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
670
671
672
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

673
674
		const int &type = static_cast<int>(eventLog->getType());
		const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
675
		*session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)",
676
677
678
			soci::use(type),
			soci::use(creationTime);

679
		return q->getLastInsertId();
680
681
	}

Ronan's avatar
Ronan committed
682
	long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
683
684
685
		shared_ptr<ConferenceEvent> conferenceEvent = static_pointer_cast<ConferenceEvent>(eventLog);

		long long eventId = -1;
686
		const long long &curChatRoomId = selectChatRoomId(conferenceEvent->getChatRoomId());
687
688
689
690
691
692
693
694
695
		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);
696

697
698
699
700
			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);

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

			if (eventLog->getType() == EventLog::Type::ConferenceTerminated)
707
				*session << "UPDATE chat_room SET flags = 1 WHERE id = :chatRoomId", soci::use(curChatRoomId);
708
		}
709
710
711
712
713
714
715

		if (chatRoomId)
			*chatRoomId = curChatRoomId;

		return eventId;
	}

Ronan's avatar
Ronan committed
716
	long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
717
718
719
720
		// TODO.
		return 0;
	}

Ronan's avatar
Ronan committed
721
722
	long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
723
724
725
726
727
728
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return -1;
		}

729
		const long long &eventId = insertConferenceEvent(eventLog);
730
731
		if (eventId < 0)
			return -1;
732

733
		soci::session *session = dbSession.getBackendSession<soci::session>();
734
735
736
737
738
739
740
741
742

		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;

743
		*session << "INSERT INTO conference_chat_message_event ("
744
			"  event_id, from_sip_address_id, to_sip_address_id,"
745
			"  time, state, direction, imdn_message_id, is_secured"
746
747
			") VALUES ("
			"  :eventId, :localSipaddressId, :remoteSipaddressId,"
748
			"  :time, :state, :direction, :imdnMessageId, :isSecured"
749
			")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId),
750
751
			soci::use(messageTime), soci::use(state), soci::use(direction),
			soci::use(imdnMessageId), soci::use(isSecured);
752

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

756
757
758
		return eventId;
	}

759
	void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
760
761
762
763
764
765
766
767
		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();
768
		MainDbKeyPrivate *dEventKey = static_cast<MainDbKey &>(dEventLog->dbKey).getPrivate();
769
		const long long &eventId = dEventKey->storageId;
770
771

		soci::session *session = dbSession.getBackendSession<soci::session>();
772
773
774
		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);
775

776
777
778
779
		//TODO: improve
		removeContentsForChatMessageEvent(eventId);
		for (const Content *content : chatMessage->getContents())
			insertContent(eventId, *content);
780
781
	}

782
783
	long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
		long long curChatRoomId;
784
		const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId);
785
786
787
		if (eventId < 0)
			return -1;

788
		const unsigned int &lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
789
790
791

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
Ronan's avatar
Ronan committed
792
			"  VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId);
793
		*session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId",
794
795
796
797
			soci::use(lastNotifyId), soci::use(curChatRoomId);

		if (chatRoomId)
			*chatRoomId = curChatRoomId;
798

799
800
801
		return eventId;
	}

Ronan's avatar
Ronan committed
802
	long long MainDbPrivate::insertConferenceParticipantEvent (const shared_ptr<EventLog> &eventLog) {
803
		long long chatRoomId;
804
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
805
806
807
		if (eventId < 0)
			return -1;

808
809
810
		shared_ptr<ConferenceParticipantEvent> participantEvent =
			static_pointer_cast<ConferenceParticipantEvent>(eventLog);

811
		const long long &participantAddressId = insertSipAddress(
812
			participantEvent->getParticipantAddress().asString()
813
814
		);

815
		soci::session *session = dbSession.getBackendSession<soci::session>();
816
		*session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)"
817
			"  VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
818

819
820
821
822
823
824
825
826
827
		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:
828
829
				deleteChatRoomParticipant(chatRoomId, participantAddressId);
				break;
830
831
832
833
834

			default:
				break;
		}

835
836
837
		return eventId;
	}

Ronan's avatar
Ronan committed
838
	long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
839
		const long long &eventId = insertConferenceParticipantEvent(eventLog);
840
841
842
		if (eventId < 0)
			return -1;

843
		const long long &deviceAddressId = insertSipAddress(
844
			static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog)->getDeviceAddress().asString()
845
846
847
		);

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

851
		return eventId;
852
853
	}

Ronan's avatar
Ronan committed
854
	long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
855
		long long chatRoomId;
856
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
857
858
859
860
		if (eventId < 0)
			return -1;

		const string &subject = static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject();
861
862

		soci::session *session = dbSession.getBackendSession<soci::session>();
863
		*session << "INSERT INTO conference_subject_event (event_id, subject)"
864
865
866
867
			"  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);
868

869
		return eventId;
870
871
	}

872
873
// -----------------------------------------------------------------------------

874
875
	shared_ptr<EventLog> MainDbPrivate::getEventFromCache (long long storageId) const {
		auto it = storageIdToEvent.find(storageId);
876
877
878
879
880
881
882
883
884
		if (it == storageIdToEvent.cend())
			return nullptr;

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

885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
	shared_ptr<ChatMessage> MainDbPrivate::getChatMessageFromCache (long long storageId) const {
		auto it = storageIdToChatMessage.find(storageId);
		if (it == storageIdToChatMessage.cend())
			return nullptr;

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

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

		EventLogPrivate *dEventLog = eventLog->getPrivate();
		L_ASSERT(!dEventLog->dbKey.isValid());
		dEventLog->dbKey = MainDbEventKey(q->getCore(), storageId);
		storageIdToEvent[storageId] = eventLog;
		L_ASSERT(dEventLog->dbKey.isValid());
	}

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

		ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
		L_ASSERT(!dChatMessage->dbKey.isValid());
		dChatMessage->dbKey = MainDbChatMessageKey(q->getCore(), storageId);
		storageIdToChatMessage[storageId] = chatMessage;
		L_ASSERT(dChatMessage->dbKey.isValid());
	}

916
	void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) {
917
		soci::session *session = dbSession.getBackendSession<soci::session>();
918
		soci::rowset<soci::row> rows = (session->prepare << query, soci::use(chatRoomId));
919
		for (const auto &row : rows) {
920
921
			long long eventId = resolveId(row, 0);
			shared_ptr<EventLog> eventLog = getEventFromCache(eventId);
922
923
924
925
926
			if (eventLog) {
				const EventLogPrivate *