events-db.cpp 10.7 KB
Newer Older
Ronan's avatar
Ronan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * events-db.cpp
 * Copyright (C) 2017  Belledonne Communications SARL
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * 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
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

19 20
#include <algorithm>

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

Ronan's avatar
Ronan committed
25
#include "abstract/abstract-db-p.h"
Ronan's avatar
Ronan committed
26 27 28
#include "event/call-event.h"
#include "event/event.h"
#include "event/message-event.h"
Ronan's avatar
Ronan committed
29
#include "logger/logger.h"
30
#include "message/message.h"
Ronan's avatar
Ronan committed
31 32 33 34 35

#include "events-db.h"

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

Ronan's avatar
Ronan committed
36 37
using namespace std;

Ronan's avatar
Ronan committed
38 39 40 41 42 43 44 45
LINPHONE_BEGIN_NAMESPACE

class EventsDbPrivate : public AbstractDbPrivate {};

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

EventsDb::EventsDb () : AbstractDb(*new EventsDbPrivate) {}

Ronan's avatar
Ronan committed
46 47 48 49
// -----------------------------------------------------------------------------
// Helpers.
// -----------------------------------------------------------------------------

50 51 52 53 54 55 56
template<typename T>
struct ToSqlPair {
	T first;
	const char *second;
};

static constexpr ToSqlPair<EventsDb::Filter> eventFilterToSql[] = {
57 58 59 60 61
	{ EventsDb::MessageFilter, "1" },
	{ EventsDb::CallFilter, "2" },
	{ EventsDb::ConferenceFilter, "3" }
};

Ronan's avatar
Ronan committed
62
static constexpr const char *mapEventFilterToSql (EventsDb::Filter filter) {
63
	return eventFilterToSql[filter].second;
Ronan's avatar
Ronan committed
64 65
}

Ronan's avatar
Ronan committed
66
static constexpr const char *mapMessageDirectionToSql (Message::Direction direction) {
67 68 69
	return direction == Message::Direction::Incoming ? "1" : "2";
}

70
static constexpr ToSqlPair<Message::State> messageStateToSql[] = {
71 72 73 74 75 76 77 78 79 80
	{ Message::Idle, "1" },
	{ Message::InProgress, "2" },
	{ Message::Delivered, "3" },
	{ Message::NotDelivered, "4" },
	{ Message::FileTransferError, "5" },
	{ Message::FileTransferDone, "6" },
	{ Message::DeliveredToUser, "7" },
	{ Message::Displayed, "8" }
};

Ronan's avatar
Ronan committed
81
static constexpr const char *mapMessageStateToSql (Message::State state) {
82
	return messageStateToSql[state].second;
83 84 85 86
}

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

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

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

Ronan's avatar
Ronan committed
97 98 99 100 101 102 103 104 105 106 107
	bool isStart = true;
	string sql;
	for (const auto &filter : filters) {
		if (!(mask & filter))
			continue;

		if (isStart) {
			isStart = false;
			sql += " WHERE ";
		} else
			sql += " OR ";
108
		sql += " event_type_id = ";
109
		sql += mapEventFilterToSql(filter);
Ronan's avatar
Ronan committed
110 111 112 113 114 115 116 117 118 119 120 121
	}

	return sql;
}

// -----------------------------------------------------------------------------
// Soci backend.
// -----------------------------------------------------------------------------

#ifdef SOCI_ENABLED

	void EventsDb::init () {
122
		L_D(EventsDb);
Ronan's avatar
Ronan committed
123
		soci::session *session = d->dbSession.getBackendSession<soci::session>();
124

Ronan's avatar
Ronan committed
125
		*session <<
126 127 128 129 130
			"CREATE TABLE IF NOT EXISTS sip_address ("
			"  id" + primaryKeyAutoIncrementStr() + ","
			"  value VARCHAR(255) NOT NULL"
			")";

131 132 133 134 135 136
		*session <<
			"CREATE TABLE IF NOT EXISTS event_type ("
			"  id" + primaryKeyAutoIncrementStr("TINYINT") + ","
			"  value VARCHAR(255) NOT NULL"
			")";

Ronan's avatar
Ronan committed
137
		*session <<
138 139
			"CREATE TABLE IF NOT EXISTS event ("
			"  id" + primaryKeyAutoIncrementStr() + ","
140 141 142 143 144
			"  event_type_id TINYINT UNSIGNED NOT NULL,"
			"  timestamp TIMESTAMP NOT NULL,"
			"  FOREIGN KEY (event_type_id)"
			"    REFERENCES event_type(id)"
			"    ON DELETE CASCADE"
145 146
			")";

Ronan's avatar
Ronan committed
147
		*session <<
148
			"CREATE TABLE IF NOT EXISTS message_state ("
149
			"  id" + primaryKeyAutoIncrementStr("TINYINT") + ","
150
			"  state VARCHAR(255) NOT NULL"
151 152
			")";

Ronan's avatar
Ronan committed
153
		*session <<
154
			"CREATE TABLE IF NOT EXISTS message_direction ("
155
			"  id" + primaryKeyAutoIncrementStr("TINYINT") + ","
156 157 158
			"  direction VARCHAR(255) NOT NULL"
			")";

Ronan's avatar
Ronan committed
159
		*session <<
160
			"CREATE TABLE IF NOT EXISTS dialog ("
161 162 163
			"  id" + primaryKeyAutoIncrementStr() + ","
			"  local_sip_address_id INT UNSIGNED NOT NULL," // Sip address used to communicate.
			"  remote_sip_address_id INT UNSIGNED NOT NULL," // Server (for conference) or user sip address.
164 165
			"  creation_timestamp TIMESTAMP NOT NULL," // Dialog creation date.
			"  last_update_timestamp TIMESTAMP NOT NULL," // Last event timestamp (call, message...).
166 167 168 169 170 171 172 173
			"  FOREIGN KEY (local_sip_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (remote_sip_address_id)"
			"    REFERENCES sip_address(id)"
			"    ON DELETE CASCADE"
			")";

Ronan's avatar
Ronan committed
174
		*session <<
175 176
			"CREATE TABLE IF NOT EXISTS message_event ("
			"  id" + primaryKeyAutoIncrementStr() + ","
177
			"  dialog_id INT UNSIGNED NOT NULL,"
178
			"  state_id TINYINT UNSIGNED NOT NULL,"
179
			"  direction_id TINYINT UNSIGNED NOT NULL,"
180
			"  imdn_message_id VARCHAR(255) NOT NULL," // See: https://tools.ietf.org/html/rfc5438#section-6.3
181 182 183 184 185 186
			"  content_type VARCHAR(255) NOT NULL,"
			"  is_secured BOOLEAN NOT NULL,"
			"  app_data VARCHAR(2048),"
			"  FOREIGN KEY (dialog_id)"
			"    REFERENCES dialog(id)"
			"    ON DELETE CASCADE,"
187 188
			"  FOREIGN KEY (state_id)"
			"    REFERENCES message_state(id)"
189 190 191 192 193
			"    ON DELETE CASCADE,"
			"  FOREIGN KEY (direction_id)"
			"    REFERENCES message_direction(id)"
			"    ON DELETE CASCADE"
			")";
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

		{
			string query = getBackend() == Mysql
				? "INSERT INTO event_type (id, value)"
				: "INSERT OR IGNORE INTO event_type (id, value)";
			query += "VALUES"
				"(1, \"Message\"),"
				"(2, \"Call\"),"
				"(3, \"Conference\")";
			if (getBackend() == Mysql)
				query += "ON DUPLICATE KEY UPDATE value = VALUES(value)";

			*session << query;
		}

		{
			string query = getBackend() == Mysql
				? "INSERT INTO message_direction (id, value)"
				: "INSERT OR IGNORE INTO message_direction (id, value)";
			query += "VALUES"
				"(1, \"Incoming\"),"
				"(2, \"Outgoing\")";
			if (getBackend() == Mysql)
				query += "ON DUPLICATE KEY UPDATE value = VALUES(value)";

			*session << query;
		}

		{
			string query = getBackend() == Mysql
				? "INSERT INTO message_state (id, value)"
				: "INSERT OR IGNORE INTO message_state (id, value)";
			query += "VALUES"
				"(1, \"Idle\"),"
				"(2, \"InProgress\"),"
				"(3, \"Delivered\"),"
				"(4, \"NotDelivered\"),"
				"(5, \"FileTransferError\"),"
				"(6, \"FileTransferDone\"),"
				"(7, \"DeliveredToUser\"),"
				"(8, \"Displayed\")";
			if (getBackend() == Mysql)
				query += "ON DUPLICATE KEY UPDATE value = VALUES(value)";

			*session << query;
		}
Ronan's avatar
Ronan committed
240
	}
241

Ronan's avatar
Ronan committed
242 243 244 245 246 247 248 249
	bool EventsDb::addEvent (const Event &event) {
		// TODO.
		switch (event.getType()) {
			case Event::None:
				return false;
			case Event::MessageEvent:
			case Event::CallStartEvent:
			case Event::CallEndEvent:
250 251 252 253 254 255
			case Event::ConferenceCreatedEvent:
			case Event::ConferenceDestroyedEvent:
			case Event::ConferenceParticipantAddedEvent:
			case Event::ConferenceParticipantRemovedEvent:
			case Event::ConferenceParticipantSetAdminEvent:
			case Event::ConferenceParticipantUnsetAdminEvent:
Ronan's avatar
Ronan committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
				break;
		}

		return true;
	}

	bool EventsDb::deleteEvent (const Event &event) {
		// TODO.
		(void)event;
		return true;
	}

	void EventsDb::cleanEvents (FilterMask mask) {
		// TODO.
		(void)mask;
	}

	int EventsDb::getEventsCount (FilterMask mask) const {
		L_D(const EventsDb);

		string query = "SELECT COUNT(*) FROM event" +
			buildSqlEventFilter({ MessageFilter, CallFilter, ConferenceFilter }, mask);
		int count = 0;

		L_BEGIN_LOG_EXCEPTION

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

		L_END_LOG_EXCEPTION

		return count;
	}

	int EventsDb::getMessagesCount (const string &remoteAddress) const {
291 292
		L_D(const EventsDb);

Ronan's avatar
Ronan committed
293 294 295 296 297 298
		string query = "SELECT COUNT(*) FROM message_event";
		if (!remoteAddress.empty())
			query += "  WHERE dialog_id = ("
				"    SELECT id FROM dialog WHERE remote_sip_address_id =("
				"      SELECT id FROM sip_address WHERE value = :remote_address"
				"    )"
299
				"  )";
300 301 302 303 304
		int count = 0;

		L_BEGIN_LOG_EXCEPTION

		soci::session *session = d->dbSession.getBackendSession<soci::session>();
305
		*session << query, soci::use(remoteAddress), soci::into(count);
306 307 308 309

		L_END_LOG_EXCEPTION

		return count;
Ronan's avatar
Ronan committed
310 311 312
	}

	int EventsDb::getUnreadMessagesCount (const string &remoteAddress) const {
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
		L_D(const EventsDb);

		string query = "SELECT COUNT(*) FROM message_event";
		if (!remoteAddress.empty())
			query += "  WHERE dialog_id = ("
				"    SELECT id FROM dialog WHERE remote_sip_address_id = ("
				"      SELECT id FROM sip_address WHERE value = :remote_address"
				"    )"
				"  )"
				"  AND direction_id = " + string(mapMessageDirectionToSql(Message::Incoming)) +
				"  AND state_id = " + string(mapMessageStateToSql(Message::Displayed));
		int count = 0;

		L_BEGIN_LOG_EXCEPTION

		soci::session *session = d->dbSession.getBackendSession<soci::session>();
		*session << query, soci::use(remoteAddress), soci::into(count);

		L_END_LOG_EXCEPTION

		return count;
Ronan's avatar
Ronan committed
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
	}

	list<Event> EventsDb::getHistory (const string &remoteAddress, int nLast, FilterMask mask) const {
		// TODO.
		(void)remoteAddress;
		(void)nLast;
		(void)mask;
		return list<Event>();
	}

	list<Event> EventsDb::getHistory (const string &remoteAddress, int begin, int end, FilterMask mask) const {
		// TODO.
		(void)remoteAddress;
		(void)begin;
		(void)end;
		(void)mask;
		return list<Event>();
	}

	void EventsDb::cleanHistory (const string &remoteAddress) {
		// TODO.
		(void)remoteAddress;
	}
Ronan's avatar
Ronan committed
357 358

// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
359 360
// No backend.
// -----------------------------------------------------------------------------
Ronan's avatar
Ronan committed
361

Ronan's avatar
Ronan committed
362 363 364 365 366 367
#else

	void EventsDb::init () {}

	bool EventsDb::addEvent (const Event &) {
		return false;
Ronan's avatar
Ronan committed
368 369
	}

Ronan's avatar
Ronan committed
370 371 372
	bool EventsDb::deleteEvent (const Event &) {
		return false;
	}
Ronan's avatar
Ronan committed
373

Ronan's avatar
Ronan committed
374
	void EventsDb::cleanEvents (FilterMask) {}
Ronan's avatar
Ronan committed
375

Ronan's avatar
Ronan committed
376 377 378
	int EventsDb::getEventsCount (FilterMask) const {
		return 0;
	}
Ronan's avatar
Ronan committed
379

Ronan's avatar
Ronan committed
380 381 382
	int EventsDb::getMessagesCount (const string &) const {
		return 0;
	}
Ronan's avatar
Ronan committed
383

Ronan's avatar
Ronan committed
384 385 386
	int EventsDb::getUnreadMessagesCount (const string &) const {
		return 0;
	}
Ronan's avatar
Ronan committed
387

Ronan's avatar
Ronan committed
388 389 390
	list<Event> EventsDb::getHistory (const string &, int, FilterMask) const {
		return list<Event>();
	}
Ronan's avatar
Ronan committed
391

Ronan's avatar
Ronan committed
392 393 394
	list<Event> EventsDb::getHistory (const string &, int, int, FilterMask) const {
		return list<Event>();
	}
Ronan's avatar
Ronan committed
395

Ronan's avatar
Ronan committed
396
	void EventsDb::cleanHistory (const string &) {}
Ronan's avatar
Ronan committed
397

Ronan's avatar
Ronan committed
398
#endif // ifdef SOCI_ENABLED
Ronan's avatar
Ronan committed
399 400

LINPHONE_END_NAMESPACE