main-db.cpp 74.8 KB
Newer Older
Ronan's avatar
Ronan committed
1
/*
2
 * main-db.cpp
Ghislain MARY's avatar
Ghislain MARY committed
3
 * Copyright (C) 2010-2017 Belledonne Communications SARL
Ronan's avatar
Ronan committed
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5
6
7
8
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
Ronan's avatar
Ronan committed
9
10
11
12
13
14
15
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
Ghislain MARY's avatar
Ghislain MARY committed
16
17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Ronan's avatar
Ronan committed
18
19
 */

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

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

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

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;

Ronan's avatar
Ronan committed
275
276
		shared_ptr<Participant> me = chatRoom->getMe();
		insertChatRoomParticipant(id, insertSipAddress(me->getAddress().asString()), me->isAdmin());
277
		for (const auto &participant : chatRoom->getParticipants())
278
			insertChatRoomParticipant(id, insertSipAddress(participant->getAddress().asString()), participant->isAdmin());
279
280

		return id;
281
282
	}

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

286
		soci::session *session = dbSession.getBackendSession<soci::session>();
287
288
		soci::statement statement = (
			session->prepare << "UPDATE chat_room_participant SET is_admin = :isAdmin"
289
				"  WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :sipAddressId",
290
				soci::use(static_cast<const int &>(isAdmin)), soci::use(chatRoomId), soci::use(sipAddressId)
291
292
		);
		statement.execute(true);
Ronan's avatar
Ronan committed
293
294
		if (statement.get_affected_rows() == 0) {
			lInfo() << "Insert new chat room participant in database: `" << sipAddressId << "` (isAdmin=" << isAdmin << ").";
295
			*session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)"
296
				"  VALUES (:chatRoomId, :sipAddressId, :isAdmin)",
297
				soci::use(chatRoomId), soci::use(sipAddressId), soci::use(static_cast<const int &>(isAdmin));
Ronan's avatar
Ronan committed
298
		}
299
300
	}

301
	void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) {
302
303
		soci::session *session = dbSession.getBackendSession<soci::session>();
		soci::statement statement = (
304
			session->prepare << "UPDATE chat_message_participant SET state = :state"
305
				"  WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId",
Ronan's avatar
Ronan committed
306
				soci::use(state), soci::use(eventId), soci::use(sipAddressId)
307
308
		);
		statement.execute(true);
309
		if (statement.get_affected_rows() == 0 && state != static_cast<int>(ChatMessage::State::Displayed))
310
			*session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)"
Ronan's avatar
Ronan committed
311
312
				"  VALUES (:eventId, :sipAddressId, :state)",
				soci::use(eventId), soci::use(sipAddressId), soci::use(state);
313
314
	}

315
	long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const {
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
		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);
	}

345
346
347
348
349
350
351
	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
352
353
// -----------------------------------------------------------------------------

354
	shared_ptr<EventLog> MainDbPrivate::selectGenericConferenceEvent (
355
		long long eventId,
356
		EventLog::Type type,
357
		time_t creationTime,
358
		const ChatRoomId &chatRoomId
359
	) const {
Ronan's avatar
Ronan committed
360
361
		shared_ptr<EventLog> eventLog;

Ronan's avatar
Ronan committed
362
363
364
365
366
		switch (type) {
			case EventLog::Type::None:
				return nullptr;

			case EventLog::Type::ConferenceCreated:
367
			case EventLog::Type::ConferenceTerminated:
Ronan's avatar
Ronan committed
368
369
				eventLog = selectConferenceEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
370

371
372
			case EventLog::Type::ConferenceCallStart:
			case EventLog::Type::ConferenceCallEnd:
Ronan's avatar
Ronan committed
373
374
				eventLog = selectConferenceCallEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
375
376

			case EventLog::Type::ConferenceChatMessage:
Ronan's avatar
Ronan committed
377
378
				eventLog = selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
379
380
381
382
383

			case EventLog::Type::ConferenceParticipantAdded:
			case EventLog::Type::ConferenceParticipantRemoved:
			case EventLog::Type::ConferenceParticipantSetAdmin:
			case EventLog::Type::ConferenceParticipantUnsetAdmin:
Ronan's avatar
Ronan committed
384
385
				eventLog = selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
386
387
388

			case EventLog::Type::ConferenceParticipantDeviceAdded:
			case EventLog::Type::ConferenceParticipantDeviceRemoved:
Ronan's avatar
Ronan committed
389
390
				eventLog = selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId);
				break;
Ronan's avatar
Ronan committed
391
392

			case EventLog::Type::ConferenceSubjectChanged:
Ronan's avatar
Ronan committed
393
394
395
396
				eventLog = selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId);
				break;
		}

397
398
		if (eventLog)
			cache(eventLog, eventId);
Ronan's avatar
Ronan committed
399

400
		return eventLog;
Ronan's avatar
Ronan committed
401
402
	}

403
	shared_ptr<EventLog> MainDbPrivate::selectConferenceEvent (
404
		long long,
405
		EventLog::Type type,
406
		time_t creationTime,
407
		const ChatRoomId &chatRoomId
408
	) const {
409
410
		return make_shared<ConferenceEvent>(
			type,
411
			creationTime,
412
			chatRoomId
413
		);
Ronan's avatar
Ronan committed
414
415
	}

416
	shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
417
		long long eventId,
418
		EventLog::Type type,
419
		time_t creationTime,
420
		const ChatRoomId &chatRoomId
421
	) const {
Ronan's avatar
Ronan committed
422
423
424
425
		// TODO.
		return nullptr;
	}

426
	shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
427
		long long eventId,
428
		EventLog::Type type,
429
		time_t creationTime,
430
		const ChatRoomId &chatRoomId
431
	) const {
Ronan's avatar
Ronan committed
432
433
434
		L_Q();

		shared_ptr<Core> core = q->getCore();
435
		shared_ptr<ChatRoom> chatRoom = core->findChatRoom(chatRoomId);
Ronan's avatar
Ronan committed
436
437
438
		if (!chatRoom)
			return nullptr;

439
		// 1 - Fetch chat message.
440
441
442
		shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
		if (chatMessage)
			goto end;
443
		{
444
445
			string fromSipAddress;
			string toSipAddress;
446
447
448

			tm messageTime;

449
			string imdnMessageId;
450

451
452
453
454
455
			int state;
			int direction;
			int isSecured;

			soci::session *session = dbSession.getBackendSession<soci::session>();
456
			*session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured"
457
458
				"  FROM event, conference_chat_message_event, sip_address AS from_sip_address,"
				"  sip_address AS to_sip_address"
459
460
				"  WHERE event_id = :eventId"
				"  AND event_id = event.id"
461
462
				"  AND from_sip_address_id = from_sip_address.id"
				"  AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress),
463
464
				soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction),
				soci::into(isSecured), soci::use(eventId);
465

466
467
468
469
			chatMessage = shared_ptr<ChatMessage>(new ChatMessage(
				chatRoom,
				static_cast<ChatMessage::Direction>(direction)
			));
470
471
			chatMessage->setIsSecured(static_cast<bool>(isSecured));

472
473
			ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
			dChatMessage->setState(static_cast<ChatMessage::State>(state), true);
474

475
476
477
478
			dChatMessage->forceFromAddress(IdentityAddress(fromSipAddress));
			dChatMessage->forceToAddress(IdentityAddress(toSipAddress));

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

481
482
483
		// 2 - Fetch contents.
		{
			soci::session *session = dbSession.getBackendSession<soci::session>();
484
485
			static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body"
				"  FROM chat_message_content, content_type"
486
487
488
				"  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) {
489
				ContentType contentType(row.get<string>(2));
490
				const long long &contentId = resolveId(row, 0);
491
492
493
494
495
				Content *content;

				if (contentType == ContentType::FileTransfer)
					content = new FileTransferContent();
				else if (contentType.isFile()) {
496
					// 2.1 - Fetch contents' file informations.
497
498
499
500
501
					string name;
					int size;
					string path;

					*session << "SELECT name, size, path FROM chat_message_file_content"
Ronan's avatar
Ronan committed
502
						"  WHERE chat_message_content_id = :contentId",
Ronan's avatar
Ronan committed
503
						soci::into(name), soci::into(size), soci::into(path), soci::use(contentId);
504
505
506
507
508
509
510
511

					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
512
					content = new Content();
513

514
515
516
517
518
519
520
521
522
523
				// 2.2 - Fetch contents' app data.
				static const string content_app_data_query = "SELECT name, data FROM chat_message_content_app_data"
					" WHERE chat_message_content_id = :messageContentId";
				soci::rowset<soci::row> content_app_data_rows = (session->prepare << content_app_data_query, soci::use(contentId));
				for (const auto &content_app_data_row : content_app_data_rows) {
					string content_app_data_name(content_app_data_row.get<string>(0));
					string content_app_data_value(content_app_data_row.get<string>(1));
					content->setAppData(content_app_data_name, content_app_data_value);
				}

524
				content->setContentType(contentType);
Sylvain Berfini's avatar
Sylvain Berfini committed
525
				content->setBody(row.get<string>(3));
526
				chatMessage->addContent(*content);
527
			}
Ronan's avatar
Ronan committed
528
		}
529

530
531
		cache(chatMessage, eventId);

532
	end:
533
		return make_shared<ConferenceChatMessageEvent>(
534
			creationTime,
535
536
			chatMessage
		);
Ronan's avatar
Ronan committed
537
538
	}

539
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantEvent (
540
		long long eventId,
541
		EventLog::Type type,
542
		time_t creationTime,
543
		const ChatRoomId &chatRoomId
544
	) const {
545
546
547
548
		unsigned int notifyId;
		string participantAddress;

		soci::session *session = dbSession.getBackendSession<soci::session>();
549
550
		*session << "SELECT notify_id, participant_address.value"
			"  FROM conference_notified_event, conference_participant_event, sip_address as participant_address"
551
			"  WHERE conference_participant_event.event_id = :eventId"
Ronan's avatar
Ronan committed
552
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
553
			"    AND participant_address.id = participant_sip_address_id",
554
555
556
557
			soci::into(notifyId), soci::into(participantAddress), soci::use(eventId);

		return make_shared<ConferenceParticipantEvent>(
			type,
558
			creationTime,
559
			chatRoomId,
560
			notifyId,
561
			IdentityAddress(participantAddress)
562
		);
Ronan's avatar
Ronan committed
563
564
	}

565
	shared_ptr<EventLog> MainDbPrivate::selectConferenceParticipantDeviceEvent (
566
		long long eventId,
567
		EventLog::Type type,
568
		time_t creationTime,
569
		const ChatRoomId &chatRoomId
570
	) const {
571
572
		unsigned int notifyId;
		string participantAddress;
573
		string deviceAddress;
574
575

		soci::session *session = dbSession.getBackendSession<soci::session>();
576
		*session << "SELECT notify_id, participant_address.value, device_address.value"
577
			"  FROM conference_notified_event, conference_participant_event, conference_participant_device_event,"
578
			"    sip_address AS participant_address, sip_address AS device_address"
579
580
			"  WHERE conference_participant_device_event.event_id = :eventId"
			"    AND conference_participant_event.event_id = conference_participant_device_event.event_id"
581
			"    AND conference_notified_event.event_id = conference_participant_event.event_id"
582
583
			"    AND participant_address.id = participant_sip_address_id"
			"    AND device_address.id = device_sip_address_id",
584
			soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId);
585
586
587

		return make_shared<ConferenceParticipantDeviceEvent>(
			type,
588
			creationTime,
589
			chatRoomId,
590
			notifyId,
591
592
			IdentityAddress(participantAddress),
			IdentityAddress(deviceAddress)
593
		);
Ronan's avatar
Ronan committed
594
595
	}

596
	shared_ptr<EventLog> MainDbPrivate::selectConferenceSubjectEvent (
597
		long long eventId,
598
		EventLog::Type type,
599
		time_t creationTime,
600
		const ChatRoomId &chatRoomId
601
602
603
604
	) const {
		unsigned int notifyId;
		string subject;

605
606
607
608
609
610
		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);
611
612

		return make_shared<ConferenceSubjectEvent>(
613
			creationTime,
614
			chatRoomId,
615
616
617
			notifyId,
			subject
		);
Ronan's avatar
Ronan committed
618
619
	}

620
621
// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
622
	long long MainDbPrivate::insertEvent (const shared_ptr<EventLog> &eventLog) {
623
624
625
		L_Q();
		soci::session *session = dbSession.getBackendSession<soci::session>();

626
627
		const int &type = static_cast<int>(eventLog->getType());
		const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime());
628
		*session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)",
629
630
631
			soci::use(type),
			soci::use(creationTime);

632
		return q->getLastInsertId();
633
634
	}

Ronan's avatar
Ronan committed
635
	long long MainDbPrivate::insertConferenceEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
636
637
638
		shared_ptr<ConferenceEvent> conferenceEvent = static_pointer_cast<ConferenceEvent>(eventLog);

		long long eventId = -1;
639
		const long long &curChatRoomId = selectChatRoomId(conferenceEvent->getChatRoomId());
640
641
642
643
644
645
646
647
648
		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);
649

650
651
652
653
			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);

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

			if (eventLog->getType() == EventLog::Type::ConferenceTerminated)
660
				*session << "UPDATE chat_room SET flags = 1 WHERE id = :chatRoomId", soci::use(curChatRoomId);
661
		}
662
663
664
665
666
667
668

		if (chatRoomId)
			*chatRoomId = curChatRoomId;

		return eventId;
	}

Ronan's avatar
Ronan committed
669
	long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr<EventLog> &eventLog) {
670
671
672
673
		// TODO.
		return 0;
	}

Ronan's avatar
Ronan committed
674
675
	long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
		shared_ptr<ChatMessage> chatMessage = static_pointer_cast<ConferenceChatMessageEvent>(eventLog)->getChatMessage();
676
677
678
679
680
681
		shared_ptr<ChatRoom> chatRoom = chatMessage->getChatRoom();
		if (!chatRoom) {
			lError() << "Unable to get a valid chat room. It was removed from database.";
			return -1;
		}

682
		const long long &eventId = insertConferenceEvent(eventLog);
683
684
		if (eventId < 0)
			return -1;
685

686
		soci::session *session = dbSession.getBackendSession<soci::session>();
687
688
689
690
691
692
693
694
695

		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;

696
		*session << "INSERT INTO conference_chat_message_event ("
697
			"  event_id, from_sip_address_id, to_sip_address_id,"
698
			"  time, state, direction, imdn_message_id, is_secured"
699
700
			") VALUES ("
			"  :eventId, :localSipaddressId, :remoteSipaddressId,"
701
			"  :time, :state, :direction, :imdnMessageId, :isSecured"
702
			")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId),
703
704
			soci::use(messageTime), soci::use(state), soci::use(direction),
			soci::use(imdnMessageId), soci::use(isSecured);
705

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

709
710
711
		return eventId;
	}

712
	void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr<EventLog> &eventLog) {
713
714
715
716
717
718
719
720
		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();
721
		MainDbKeyPrivate *dEventKey = static_cast<MainDbKey &>(dEventLog->dbKey).getPrivate();
722
		const long long &eventId = dEventKey->storageId;
723
724

		soci::session *session = dbSession.getBackendSession<soci::session>();
725
726
727
		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);
728

729
730
731
732
		//TODO: improve
		removeContentsForChatMessageEvent(eventId);
		for (const Content *content : chatMessage->getContents())
			insertContent(eventId, *content);
733
734
	}

735
736
	long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr<EventLog> &eventLog, long long *chatRoomId) {
		long long curChatRoomId;
737
		const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId);
738
739
740
		if (eventId < 0)
			return -1;

741
		const unsigned int &lastNotifyId = static_pointer_cast<ConferenceNotifiedEvent>(eventLog)->getNotifyId();
742
743
744

		soci::session *session = dbSession.getBackendSession<soci::session>();
		*session << "INSERT INTO conference_notified_event (event_id, notify_id)"
Ronan's avatar
Ronan committed
745
			"  VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId);
746
		*session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId",
747
748
749
750
			soci::use(lastNotifyId), soci::use(curChatRoomId);

		if (chatRoomId)
			*chatRoomId = curChatRoomId;
751

752
753
754
		return eventId;
	}

Ronan's avatar
Ronan committed
755
	long long MainDbPrivate::insertConferenceParticipantEvent (const shared_ptr<EventLog> &eventLog) {
756
		long long chatRoomId;
757
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
758
759
760
		if (eventId < 0)
			return -1;

761
762
763
		shared_ptr<ConferenceParticipantEvent> participantEvent =
			static_pointer_cast<ConferenceParticipantEvent>(eventLog);

764
		const long long &participantAddressId = insertSipAddress(
765
			participantEvent->getParticipantAddress().asString()
766
767
		);

768
		soci::session *session = dbSession.getBackendSession<soci::session>();
769
		*session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)"
770
			"  VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId);
771

772
773
774
775
776
777
778
779
780
		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:
781
782
				deleteChatRoomParticipant(chatRoomId, participantAddressId);
				break;
783
784
785
786
787

			default:
				break;
		}

788
789
790
		return eventId;
	}

Ronan's avatar
Ronan committed
791
	long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr<EventLog> &eventLog) {
792
		const long long &eventId = insertConferenceParticipantEvent(eventLog);
793
794
795
		if (eventId < 0)
			return -1;

796
		const long long &deviceAddressId = insertSipAddress(
797
			static_pointer_cast<ConferenceParticipantDeviceEvent>(eventLog)->getDeviceAddress().asString()
798
799
800
		);

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

804
		return eventId;
805
806
	}

Ronan's avatar
Ronan committed
807
	long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr<EventLog> &eventLog) {
808
		long long chatRoomId;
809
		const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId);
810
811
812
813
		if (eventId < 0)
			return -1;

		const string &subject = static_pointer_cast<ConferenceSubjectEvent>(eventLog)->getSubject();
814
815

		soci::session *session = dbSession.getBackendSession<soci::session>();
816
		*session << "INSERT INTO conference_subject_event (event_id, subject)"
817
818
819
820
			"  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);
821

822
		return eventId;
823
824
	}

825
826
// -----------------------------------------------------------------------------

827
828
	shared_ptr<EventLog> MainDbPrivate::getEventFromCache (long long storageId) const {
		auto it = storageIdToEvent.find(storageId);
829
830
831
832
833
834
835
836
837
		if (it == storageIdToEvent.cend())
			return nullptr;

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

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
	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());
	}

869
	void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) {
870
		soci::session *session = dbSession.getBackendSession<soci::session>();
871
		soci::rowset<soci::row> rows = (session->prepare << query, soci::use(chatRoomId));
872
		for (const auto &row : rows) {
873
874
			long long eventId = resolveId(row, 0);
			shared_ptr<EventLog> eventLog = getEventFromCache(eventId);
875
876
877
878
879
			if (eventLog) {
				const EventLogPrivate *dEventLog = eventLog->getPrivate();
				L_ASSERT(dEventLog->dbKey.isValid());
				dEventLog->dbKey = MainDbEventKey();
			}
880
881
882
883
884
885
886
			// TODO: Try to add a better code here...
			shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
			if (chatMessage) {
				const ChatMessagePrivate *dChatMessage = chatMessage->getPrivate();
				L_ASSERT(dChatMessage->dbKey.isValid());
				dChatMessage->dbKey = MainDbChatMessageKey();
			}
887
888
889
		}
	}

890
// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
891

892
	void MainDb::init () {
893
		L_D();
894
895

		const string charset = getBackend() == Mysql ? "DEFAULT CHARSET=utf8" : "";
Ronan's avatar
Ronan committed
896
		soci::session *session = d->dbSession.getBackendSession<soci::session>();
897

Ronan's avatar
Ronan committed
898
		*session <<