message_storage.c 26.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
message_storage.c
Copyright (C) 2012  Belledonne Communications, Grenoble, France

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.

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, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 19 20
*/

#include "private.h"
21
#include "linphone/core.h"
22

23

24
#ifdef SQLITE_STORAGE_ENABLED
25

26 27 28
#ifndef PRIu64
#define PRIu64 "I64u"
#endif
29

30
#ifndef _WIN32
31
#if !defined(__QNXNTO__)
32 33 34 35
#include <langinfo.h>
#include <locale.h>
#include <iconv.h>
#include <string.h>
Simon Morlat's avatar
Simon Morlat committed
36
#endif
37 38 39 40 41 42
#else
#include <Windows.h>
#endif

#define MAX_PATH_SIZE 1024

43
#include "sqlite3.h"
44
#include <assert.h>
45

Sandrine Avakian's avatar
Sandrine Avakian committed
46

47 48 49 50 51 52
static char *utf8_convert(const char *filename){
	char db_file_utf8[MAX_PATH_SIZE] = "";
#if defined(_WIN32)
	wchar_t db_file_utf16[MAX_PATH_SIZE]={0};
	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, filename, -1, db_file_utf16, MAX_PATH_SIZE);
	WideCharToMultiByte(CP_UTF8, 0, db_file_utf16, -1, db_file_utf8, sizeof(db_file_utf8), NULL, NULL);
53 54
#elif defined(__QNXNTO__)
	strncpy(db_file_utf8, filename, MAX_PATH_SIZE - 1);
55 56 57 58 59 60
#else
	char db_file_locale[MAX_PATH_SIZE] = {'\0'};
	char *inbuf=db_file_locale, *outbuf=db_file_utf8;
	size_t inbyteleft = MAX_PATH_SIZE, outbyteleft = MAX_PATH_SIZE;
	iconv_t cb;

61 62 63 64 65 66 67 68 69 70 71
	if (strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) {
		strncpy(db_file_utf8, filename, MAX_PATH_SIZE - 1);
	} else {
		strncpy(db_file_locale, filename, MAX_PATH_SIZE-1);
		cb = iconv_open("UTF-8", nl_langinfo(CODESET));
		if (cb != (iconv_t)-1) {
			int ret;
			ret = iconv(cb, &inbuf, &inbyteleft, &outbuf, &outbyteleft);
			if(ret == -1) db_file_utf8[0] = '\0';
			iconv_close(cb);
		}
72 73
	}
#endif
74 75
	return ms_strdup(db_file_utf8);
}
Sandrine Avakian's avatar
Sandrine Avakian committed
76

77 78 79 80 81 82 83 84 85 86 87

int _linphone_sqlite3_open(const char *db_file, sqlite3 **db) {
	char* errmsg = NULL;
	int ret;
	int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;

#if TARGET_OS_IPHONE
	/* the secured filesystem of the iPHone doesn't allow writing while the app is in background mode, which is problematic.
	 * We workaround by asking that the open is made with no protection*/
	flags |= SQLITE_OPEN_FILEPROTECTION_NONE;
#endif
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
88

Sandrine Avakian's avatar
Sandrine Avakian committed
89 90 91
	/*since we plug our vfs into sqlite, we convert to UTF-8.
	 * On Windows, the filename has to be converted back to windows native charset.*/
	char *utf8_filename = utf8_convert(db_file);
Sylvain Berfini's avatar
Sylvain Berfini committed
92
	ret = sqlite3_open_v2(utf8_filename, db, flags, LINPHONE_SQLITE3_VFS);
Sandrine Avakian's avatar
Sandrine Avakian committed
93
	ms_free(utf8_filename);
94

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
	if (ret != SQLITE_OK) return ret;
	// Some platforms do not provide a way to create temporary files which are needed
	// for transactions... so we work in memory only
	// see http ://www.sqlite.org/compile.html#temp_store
	ret = sqlite3_exec(*db, "PRAGMA temp_store=MEMORY", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Cannot set sqlite3 temporary store to memory: %s.", errmsg);
		sqlite3_free(errmsg);
	}
#if TARGET_OS_IPHONE
	ret = sqlite3_exec(*db, "PRAGMA journal_mode = OFF", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Cannot set sqlite3 journal_mode to off: %s.", errmsg);
		sqlite3_free(errmsg);
	}
#endif
	return ret;
}
#endif



117
#ifdef SQLITE_STORAGE_ENABLED
118 119


120 121 122 123 124 125 126 127 128 129 130
static LinphoneChatMessage * get_weak_message(LinphoneChatRoom *cr, unsigned int storage_id) {
	LinphoneChatMessage *cm;
	bctbx_list_t *item;
	for (item = cr->weak_messages; item != NULL; item = bctbx_list_next(item)) {
		cm = (LinphoneChatMessage *)bctbx_list_get_data(item);
		if (linphone_chat_message_get_storage_id(cm) == storage_id)
			return linphone_chat_message_ref(cm);
	}
	return NULL;
}

131
static ORTP_INLINE LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){
132
	bctbx_list_t* transients = cr->transient_messages;
133 134 135 136 137 138 139 140 141 142 143
	LinphoneChatMessage* chat;
	while( transients ){
		chat = (LinphoneChatMessage*)transients->data;
		if(chat->storage_id == storage_id){
			return linphone_chat_message_ref(chat);
		}
		transients = transients->next;
	}
	return NULL;
}

144 145 146 147 148 149 150
/* DB layout:
 * | 0  | storage_id
 * | 1  | type
 * | 2  | subtype
 * | 3  | name
 * | 4  | encoding
 * | 5  | size
151
 * | 6  | data (currently not stored)
152 153
 * | 7  | key size
 * | 8  | key
154 155 156 157
 */
// Callback for sql request when getting linphone content
static int callback_content(void *data, int argc, char **argv, char **colName) {
	LinphoneChatMessage *message = (LinphoneChatMessage *)data;
158

159
	if (message->file_transfer_information) {
160
		linphone_content_unref(message->file_transfer_information);
161 162
		message->file_transfer_information = NULL;
	}
163 164 165 166 167 168
	message->file_transfer_information = linphone_content_new();
	if (argv[1]) linphone_content_set_type(message->file_transfer_information, argv[1]);
	if (argv[2]) linphone_content_set_subtype(message->file_transfer_information, argv[2]);
	if (argv[3]) linphone_content_set_name(message->file_transfer_information, argv[3]);
	if (argv[4]) linphone_content_set_encoding(message->file_transfer_information, argv[4]);
	linphone_content_set_size(message->file_transfer_information, (size_t)atoi(argv[5]));
169
	if (argv[8]) linphone_content_set_key(message->file_transfer_information, argv[8], (size_t)atol(argv[7]));
170

171 172 173 174 175 176 177
	return 0;
}

static void fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *message, int content_id) {
	char* errmsg = NULL;
	int ret;
	char * buf;
178

179 180 181 182 183 184 185 186
	buf = sqlite3_mprintf("SELECT * FROM content WHERE id = %i", content_id);
	ret = sqlite3_exec(db, buf, callback_content, message, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Error in creation: %s.", errmsg);
		sqlite3_free(errmsg);
	}
	sqlite3_free(buf);
}
187

188 189 190 191 192 193


// Called when fetching all conversations from database
static int callback_all(void *data, int argc, char **argv, char **colName){
	LinphoneCore* lc = (LinphoneCore*) data;
	char* address = argv[0];
Simon Morlat's avatar
Simon Morlat committed
194 195 196
	LinphoneAddress *addr = linphone_address_new(address);
	if (addr){
		linphone_core_get_chat_room(lc, addr);
Simon Morlat's avatar
Simon Morlat committed
197
		linphone_address_unref(addr);
Simon Morlat's avatar
Simon Morlat committed
198
	}
199 200 201
	return 0;
}

202 203 204 205 206 207 208 209 210 211 212 213
/* DB layout:
 * | 0  | storage_id
 * | 1  | localContact
 * | 2  | remoteContact
 * | 3  | direction flag
 * | 4  | message
 * | 5  | time (unused now, used to be string-based timestamp)
 * | 6  | read flag
 * | 7  | status
 * | 8  | external body url
 * | 9  | utc timestamp
 * | 10 | app data text
214
 * | 11 | linphone content id
Ghislain MARY's avatar
Ghislain MARY committed
215
 * | 12 | message id
216
 */
217
static int create_chat_message(void *data, int argc, char **argv, char **colName){
218
	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
219
	unsigned int storage_id = (unsigned int)atoi(argv[0]);
220
	LinphoneChatMessage* new_message;
221

222 223 224 225 226 227 228
	/* Check if the message exists in the weak messages list, in which case we should return that one. */
	new_message = get_weak_message(cr, storage_id);
	if (new_message == NULL) {
		/* Check if the message exists in the transient list, in which case we should return that one. */
		new_message = get_transient_message(cr, storage_id);
	}
	if (new_message == NULL) {
229 230 231 232
		new_message = linphone_chat_room_create_message(cr, argv[4]);

		if(atoi(argv[3])==LinphoneChatMessageIncoming){
			new_message->dir=LinphoneChatMessageIncoming;
233
			linphone_chat_message_set_from(new_message,linphone_chat_room_get_peer_address(cr));
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
234
			new_message->to = NULL; /*will be filled at the end */
235 236
		} else {
			new_message->dir=LinphoneChatMessageOutgoing;
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
237
			new_message->from = NULL; /*will be filled at the end */
238
			linphone_chat_message_set_to(new_message,linphone_chat_room_get_peer_address(cr));
239
		}
240

241
		new_message->time = (time_t)atol(argv[9]);
242 243 244
		new_message->is_read=atoi(argv[6]);
		new_message->state=atoi(argv[7]);
		new_message->storage_id=storage_id;
245
		new_message->external_body_url= ms_strdup(argv[8]);
246
		new_message->appdata = ms_strdup(argv[10]);
Ghislain MARY's avatar
Ghislain MARY committed
247
		new_message->message_id = ms_strdup(argv[12]);
248

249 250 251 252 253 254
		if (argv[11] != NULL) {
			int id = atoi(argv[11]);
			if (id >= 0) {
				fetch_content_from_database(cr->lc->db, new_message, id);
			}
		}
255 256 257

		/* Add the new message to the weak messages list. */
		linphone_chat_room_add_weak_message(cr, new_message);
258
	}
259
	cr->messages_hist=bctbx_list_prepend(cr->messages_hist,new_message);
260 261 262 263 264 265 266

	return 0;
}

void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){
	char* errmsg=NULL;
	int ret;
267
	ret=sqlite3_exec(db,stmt,create_chat_message,cr,&errmsg);
268
	if(ret != SQLITE_OK) {
269
		ms_error("Error in creation: %s.", errmsg);
270 271 272 273
		sqlite3_free(errmsg);
	}
}

274
int linphone_sql_request(sqlite3* db,const char *stmt){
275 276 277 278
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
279
		ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg);
280 281
		sqlite3_free(errmsg);
	}
282
	return ret;
283 284 285 286 287 288 289 290
}

// Process the request to fetch all chat contacts
void linphone_sql_request_all(sqlite3* db,const char *stmt, LinphoneCore* lc){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,callback_all,lc,&errmsg);
	if(ret != SQLITE_OK) {
291
		ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.", errmsg);
292 293 294 295
		sqlite3_free(errmsg);
	}
}

296
static int linphone_chat_message_store_content(LinphoneChatMessage *msg) {
Simon Morlat's avatar
Simon Morlat committed
297
	LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room);
298 299 300
	int id = -1;
	if (lc->db) {
		LinphoneContent *content = msg->file_transfer_information;
301
		char *buf = sqlite3_mprintf("INSERT INTO content VALUES(NULL,%Q,%Q,%Q,%Q,%i,%Q,%lld,%Q);",
302 303 304 305 306
						linphone_content_get_type(content),
						linphone_content_get_subtype(content),
						linphone_content_get_name(content),
						linphone_content_get_encoding(content),
						linphone_content_get_size(content),
307 308 309
						NULL,
						(int64_t)linphone_content_get_key_size(content),
						linphone_content_get_key(content)
310
					);
311 312 313 314 315 316 317
		linphone_sql_request(lc->db, buf);
		sqlite3_free(buf);
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
	}
	return id;
}

318
unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){
Simon Morlat's avatar
Simon Morlat committed
319
	LinphoneCore *lc=linphone_chat_room_get_core(msg->chat_room);
320
	int id = 0;
321 322

	if (lc->db){
323
		int content_id = -1;
324 325 326
		char *peer;
		char *local_contact;
		char *buf;
327 328 329
		if (msg->file_transfer_information) {
			content_id = linphone_chat_message_store_content(msg);
		}
330 331 332

		peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room));
		local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg));
Ghislain MARY's avatar
Ghislain MARY committed
333
		buf = sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%lld,%Q,%i,%Q);",
334
						local_contact,
335 336 337 338
						peer,
						msg->dir,
						msg->message,
						"-1", /* use UTC field now */
339
						FALSE, /* use state == LinphoneChatMessageStateDisplayed now */
340 341
						msg->state,
						msg->external_body_url,
Simon Morlat's avatar
Simon Morlat committed
342
						(int64_t)msg->time,
343
						msg->appdata,
Ghislain MARY's avatar
Ghislain MARY committed
344 345
						content_id,
						msg->message_id
346
					);
347 348 349 350 351 352 353 354 355
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
		ms_free(local_contact);
		ms_free(peer);
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
	}
	return id;
}

356 357
void linphone_chat_message_store_state(LinphoneChatMessage *msg){
	LinphoneCore *lc=msg->chat_room->lc;
358
	if (lc->db){
359
		char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (id = %u);",
360
								  msg->state,msg->storage_id);
361
		linphone_sql_request(lc->db,buf);
362 363 364 365
		sqlite3_free(buf);
	}
}

366 367
void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){
	LinphoneCore *lc=msg->chat_room->lc;
368
	if (lc->db){
369
		char *buf=sqlite3_mprintf("UPDATE history SET appdata=%Q WHERE id=%u;",
370 371 372 373 374 375
								  msg->appdata,msg->storage_id);
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
	}
}

376
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
Simon Morlat's avatar
Simon Morlat committed
377
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
378
	bctbx_list_t *item;
379 380
	char *peer;
	char *buf;
381 382

	if (lc->db==NULL) return ;
383

384 385
	// optimization: do not modify the database if no message is marked as unread
	if(linphone_chat_room_get_unread_messages_count(cr) == 0) return;
386

387
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
388 389 390 391 392 393 394 395 396 397 398
	buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND direction=%i", peer, LinphoneChatMessageIncoming);
	linphone_sql_request_message(lc->db, buf, cr);
	sqlite3_free(buf);
	for (item = cr->messages_hist; item != NULL; item = bctbx_list_next(item)) {
		LinphoneChatMessage *cm = (LinphoneChatMessage *)bctbx_list_get_data(item);
		linphone_chat_message_send_display_notification(cm);
	}
	bctbx_list_free_with_data(cr->messages_hist, (bctbx_list_free_func)linphone_chat_message_unref);
	cr->messages_hist = NULL;
	buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE remoteContact=%Q AND direction=%i;",
		LinphoneChatMessageStateDisplayed, peer, LinphoneChatMessageIncoming);
399 400 401
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);
402

403
	cr->unread_count = 0;
404 405 406
}

void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
Simon Morlat's avatar
Simon Morlat committed
407
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
408
	char *buf;
409 410 411

	if (lc->db==NULL) return ;

412
	buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%u;",msg->external_body_url,msg->storage_id);
413 414 415 416
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
}

417
static int linphone_chat_room_get_messages_count(LinphoneChatRoom *cr, bool_t unread_only){
Simon Morlat's avatar
Simon Morlat committed
418
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
419
	int numrows=0;
420 421
	char *peer;
	char *buf;
Ghislain MARY's avatar
Ghislain MARY committed
422
	char *option = NULL;
423 424
	sqlite3_stmt *selectStatement;
	int returnValue;
425 426

	if (lc->db==NULL) return 0;
427

428 429
	// optimization: do not read database if the count is already available in memory
	if(unread_only && cr->unread_count >= 0) return cr->unread_count;
430

431
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
432 433 434 435
	if (unread_only) {
		option = bctbx_strdup_printf("AND status!=%i AND direction=%i", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming);
	}
	buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;",peer,unread_only?option:"");
436
	returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL);
437 438 439 440 441 442 443 444
	if (returnValue == SQLITE_OK){
		if(sqlite3_step(selectStatement) == SQLITE_ROW){
			numrows= sqlite3_column_int(selectStatement, 0);
		}
	}
	sqlite3_finalize(selectStatement);
	sqlite3_free(buf);
	ms_free(peer);
445

446 447
	/* no need to test the sign of cr->unread_count here
	 * because it has been tested above */
448 449
	if(unread_only) {
		cr->unread_count = numrows;
Ghislain MARY's avatar
Ghislain MARY committed
450
		if (option) bctbx_free(option);
451
	}
452

453 454 455
	return numrows;
}

456 457 458 459 460 461 462 463
int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	return linphone_chat_room_get_messages_count(cr, TRUE);
}

int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){
	return linphone_chat_room_get_messages_count(cr, FALSE);
}

464 465
void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=cr->lc;
466
	char *buf;
467 468 469

	if (lc->db==NULL) return ;

470
	buf=sqlite3_mprintf("DELETE FROM history WHERE id = %u;", msg->storage_id);
471 472
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
473

474 475 476
	/* Invalidate unread_count when we modify the database, so that next
	 time we need it it will be recomputed from latest database state */
	cr->unread_count = -1;
477 478 479 480
}

void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
	LinphoneCore *lc=cr->lc;
481 482
	char *peer;
	char *buf;
483 484 485

	if (lc->db==NULL) return ;

486 487
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer);
488 489 490
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);
491

492
	if(cr->unread_count > 0) cr->unread_count = 0;
493 494
}

495
bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm){
Simon Morlat's avatar
Simon Morlat committed
496
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
497
	bctbx_list_t *ret;
498
	char *buf,*buf2;
499 500
	char *peer;
	uint64_t begin,end;
501
	int buf_max_size = 512;
502 503

	if (lc->db==NULL) return NULL;
504 505
	peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));

506
	cr->messages_hist = NULL;
507 508 509 510 511

	/*since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf*/
	buf=ms_malloc(buf_max_size);
	buf=sqlite3_snprintf(buf_max_size-1,buf,"SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC",peer);

512

513 514
	if (startm<0) startm=0;

515
	if ((endm>0&&endm>=startm) || (startm == 0 && endm == 0) ){
516 517 518
		buf2=ms_strdup_printf("%s LIMIT %i ",buf,endm+1-startm);
		ms_free(buf);
		buf = buf2;
519
	}else if(startm>0){
520 521 522 523
		ms_message("%s(): end is lower than start (%d < %d). Assuming no end limit.",__FUNCTION__,endm,startm);
		buf2=ms_strdup_printf("%s LIMIT -1",buf);
		ms_free(buf);
		buf = buf2;
524 525 526
	}

	if (startm>0){
527 528 529
		buf2=ms_strdup_printf("%s OFFSET %i ",buf,startm);
		ms_free(buf);
		buf = buf2;
530
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
531

Simon Morlat's avatar
Simon Morlat committed
532 533 534
	begin=ortp_get_cur_time_ms();
	linphone_sql_request_message(lc->db,buf,cr);
	end=ortp_get_cur_time_ms();
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
535

536 537 538 539
	if (endm+1-startm > 1) {
		//display message only if at least 2 messages are loaded
		ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin));
	}
540
	ms_free(buf);
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557

	if (cr->messages_hist) {
		//fill local addr with core identity instead of per message
		LinphoneAddress* local_addr = linphone_address_new(linphone_core_get_identity(cr->lc));
		bctbx_list_t* it = cr->messages_hist;
		while (it) {
			LinphoneChatMessage* msg = it->data;
			if (msg->dir == LinphoneChatMessageOutgoing) {
				msg->from = linphone_address_ref(local_addr);
			} else {
				msg->to = linphone_address_ref(local_addr);
			}
			it = it->next;
		}
		linphone_address_unref(local_addr);
	}

558 559 560 561 562 563
	ret=cr->messages_hist;
	cr->messages_hist=NULL;
	ms_free(peer);
	return ret;
}

564
bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
565
	return linphone_chat_room_get_history_range(cr, 0, nb_message-1);
566 567
}

Ghislain MARY's avatar
Ghislain MARY committed
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
LinphoneChatMessage * linphone_chat_room_find_message(LinphoneChatRoom *cr, const char *message_id) {
	LinphoneCore *lc = linphone_chat_room_get_core(cr);
	LinphoneChatMessage *cm = NULL;
	char *buf;
	char *peer;

	if (lc->db == NULL) return NULL;
	peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	cr->messages_hist = NULL;
	buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND messageId = %Q", peer, message_id);
	linphone_sql_request_message(lc->db, buf, cr);
	sqlite3_free(buf);

	if (cr->messages_hist) {
		cm = (LinphoneChatMessage *)bctbx_list_nth_data(cr->messages_hist, 0);
	}

	cr->messages_hist = NULL;
	ms_free(peer);
	return cm;
}

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
void linphone_create_table(sqlite3* db){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history ("
							 "id            INTEGER PRIMARY KEY AUTOINCREMENT,"
							 "localContact  TEXT NOT NULL,"
							 "remoteContact TEXT NOT NULL,"
							 "direction     INTEGER,"
							 "message       TEXT,"
							 "time          TEXT NOT NULL,"
							 "read          INTEGER,"
							 "status        INTEGER"
						");",
			0,0,&errmsg);
	if(ret != SQLITE_OK) {
		ms_error("Error in creation: %s.\n", errmsg);
		sqlite3_free(errmsg);
	}
}


static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
static time_t parse_time_from_db( const char* time ){
	/* messages used to be stored in the DB by using string-based time */
	struct tm ret={0};
	char tmp1[80]={0};
	char tmp2[80]={0};
	int i,j;
	time_t parsed = 0;

	if( sscanf(time,"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday,
			   &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year) == 7 ){
		ret.tm_year-=1900;
		for(i=0;i<7;i++) {
			if(strcmp(tmp1,days[i])==0) ret.tm_wday=i;
		}
		for(j=0;j<12;j++) {
			if(strcmp(tmp2,months[j])==0) ret.tm_mon=j;
		}
		ret.tm_isdst=-1;
		parsed = mktime(&ret);
	}
	return parsed;
}


637
static int migrate_messages_timestamp(void* data,int argc, char** argv, char** column_names) {
638 639 640
	time_t new_time = parse_time_from_db(argv[1]);
	if( new_time ){
		/* replace 'time' by -1 and set 'utc' to the timestamp */
641
		char *buf =	sqlite3_mprintf("UPDATE history SET utc=%lld,time='-1' WHERE id=%i;", (int64_t)new_time, atoi(argv[0]));
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
		if( buf) {
			linphone_sql_request((sqlite3*)data, buf);
			sqlite3_free(buf);
		}
	} else {
		ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]);
	}
	return 0;
}

static void linphone_migrate_timestamps(sqlite3* db){
	int ret;
	char* errmsg = NULL;
	uint64_t begin=ortp_get_cur_time_ms();

	linphone_sql_request(db,"BEGIN TRANSACTION");

659
	ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1';", migrate_messages_timestamp, db, &errmsg);
660 661 662 663 664
	if( ret != SQLITE_OK ){
		ms_warning("Error migrating outgoing messages: %s.\n", errmsg);
		sqlite3_free(errmsg);
		linphone_sql_request(db, "ROLLBACK");
	} else {
665
		uint64_t end;
666
		linphone_sql_request(db, "COMMIT");
667
		end=ortp_get_cur_time_ms();
Simon Morlat's avatar
Simon Morlat committed
668
		ms_message("Migrated message timestamps to UTC in %lu ms",(unsigned long)(end-begin));
669 670 671 672 673
	}
}

void linphone_update_table(sqlite3* db) {
	char* errmsg=NULL;
674
	char *buf;
675 676 677 678 679 680 681 682
	int ret;

	// for image url storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
683
		ms_debug("Table history updated successfully for URL.");
684 685 686 687 688 689 690 691
	}

	// for UTC timestamp storage
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg);
	if( ret != SQLITE_OK ){
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
692
		ms_debug("Table history updated successfully for UTC.");
693 694 695
		// migrate from old text-based timestamps to unix time-based timestamps
		linphone_migrate_timestamps(db);
	}
696 697 698 699 700 701 702

	// new field for app-specific storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN appdata TEXT;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
		ms_debug("Table history updated successfully for app-specific data.");
	}

	// new field for linphone content storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN content INTEGER;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table history updated successfully for content data.");
		ret = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS content ("
							"id INTEGER PRIMARY KEY AUTOINCREMENT,"
							"type TEXT,"
							"subtype TEXT,"
							"name TEXT,"
							"encoding TEXT,"
							"size INTEGER,"
							"data BLOB"
						");",
			0,0,&errmsg);
		if(ret != SQLITE_OK) {
			ms_error("Error in creation: %s.\n", errmsg);
			sqlite3_free(errmsg);
		} else {
			ms_debug("Table content successfully created.");
		}
729
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
730

731 732 733 734 735 736 737 738 739 740 741 742 743 744
	// new fields for content key storage when using lime
	ret=sqlite3_exec(db,"ALTER TABLE content ADD COLUMN key_size INTEGER;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ret=sqlite3_exec(db,"ALTER TABLE content ADD COLUMN key TEXT;",NULL,NULL,&errmsg);
		if(ret != SQLITE_OK) {
			ms_message("Table already up to date: %s.", errmsg);
			sqlite3_free(errmsg);
		} else {
			ms_debug("Table history content successfully for lime key storage data.");
		}
	}
Ghislain MARY's avatar
Ghislain MARY committed
745 746 747 748 749 750 751 752

	// new field for message_id
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN messageId TEXT;", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_message("Table already up to date: %s", errmsg);
	} else {
		ms_message("Table history updated successfully for message_id data.");
	}
753 754 755 756 757

	// Convert is_read to LinphoneChatMessageStateDisplayed
	buf = sqlite3_mprintf("UPDATE history SET status=%i WHERE read=1 AND direction=%i;", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming);
	linphone_sql_request(db, buf);
	sqlite3_free(buf);
758 759 760 761 762 763 764 765 766 767 768
}

void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
	char *buf;

	if (lc->db==NULL) return;
	buf=sqlite3_mprintf("SELECT remoteContact FROM history GROUP BY remoteContact;");
	linphone_sql_request_all(lc->db,buf,lc);
	sqlite3_free(buf);
}

769
static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){
770
	ms_warning("SQL statement '%s' took %" PRIu64 " microseconds", statement, (uint64_t)(duration / 1000LL) );
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
}

static void linphone_message_storage_activate_debug(sqlite3* db, bool_t debug){
	if( debug  ){
		sqlite3_profile(db, _linphone_message_storage_profile, NULL );
	} else {
		sqlite3_profile(db, NULL, NULL );
	}
}

void linphone_core_message_storage_set_debug(LinphoneCore *lc, bool_t debug){

	lc->debug_storage = debug;

	if( lc->db ){
		linphone_message_storage_activate_debug(lc->db, debug);
	}
}

790 791 792
void linphone_core_message_storage_init(LinphoneCore *lc){
	int ret;
	const char *errmsg;
793
	sqlite3 *db = NULL;
794

795 796
	linphone_core_message_storage_close(lc);

797
	ret=_linphone_sqlite3_open(lc->chat_db_file,&db);
798 799 800 801
	if(ret != SQLITE_OK) {
		errmsg=sqlite3_errmsg(db);
		ms_error("Error in the opening: %s.\n", errmsg);
		sqlite3_close(db);
802
		return;
803
	}
804 805 806

	linphone_message_storage_activate_debug(db, lc->debug_storage);

807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
	linphone_create_table(db);
	linphone_update_table(db);
	lc->db=db;

	// Create a chatroom for each contact in the chat history
	linphone_message_storage_init_chat_rooms(lc);
}

void linphone_core_message_storage_close(LinphoneCore *lc){
	if (lc->db){
		sqlite3_close(lc->db);
		lc->db=NULL;
	}
}

#else

unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){
	return 0;
}

828
void linphone_chat_message_store_state(LinphoneChatMessage *cr){
829 830
}

831
void linphone_chat_message_store_appdata(LinphoneChatMessage *msg){
832 833
}

834 835 836
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
}

837
bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
838 839 840
	return NULL;
}

Ghislain MARY's avatar
Ghislain MARY committed
841 842 843 844 845
bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end){
	return NULL;
}

LinphoneChatMessage * linphone_chat_room_find_message(LinphoneChatRoom *cr, const char *message_id) {
846 847 848
	return NULL;
}

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
}

void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
}

void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
}

void linphone_core_message_storage_init(LinphoneCore *lc){
}

void linphone_core_message_storage_close(LinphoneCore *lc){
}

void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
}

int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	return 0;
}

871 872 873 874
int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){
	return 0;
}

875
#endif