message_storage.c 23.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
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
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "private.h"
#include "linphonecore.h"

23

24
#ifdef SQLITE_STORAGE_ENABLED
25

Sylvain Berfini's avatar
Sylvain Berfini committed
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


Sylvain Berfini's avatar
Sylvain Berfini committed
120
static ORTP_INLINE LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){
121
	bctbx_list_t* transients = cr->transient_messages;
122 123 124 125 126 127 128 129 130 131 132
	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;
}

133 134 135 136 137 138 139
/* DB layout:
 * | 0  | storage_id
 * | 1  | type
 * | 2  | subtype
 * | 3  | name
 * | 4  | encoding
 * | 5  | size
140
 * | 6  | data (currently not stored)
141 142
 * | 7  | key size
 * | 8  | key
143 144 145 146
 */
// Callback for sql request when getting linphone content
static int callback_content(void *data, int argc, char **argv, char **colName) {
	LinphoneChatMessage *message = (LinphoneChatMessage *)data;
147

148
	if (message->file_transfer_information) {
149
		linphone_content_unref(message->file_transfer_information);
150 151
		message->file_transfer_information = NULL;
	}
152 153 154 155 156 157
	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]));
158
	if (argv[8]) linphone_content_set_key(message->file_transfer_information, argv[8], (size_t)atol(argv[7]));
159

160 161 162 163 164 165 166
	return 0;
}

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

168 169 170 171 172 173 174 175
	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);
}
176

177 178 179 180 181 182


// 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
183 184 185 186 187
	LinphoneAddress *addr = linphone_address_new(address);
	if (addr){
		linphone_core_get_chat_room(lc, addr);
		linphone_address_destroy(addr);
	}
188 189 190
	return 0;
}

191 192 193 194 195 196 197 198 199 200 201 202
/* 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
203
 * | 11 | linphone content id
204
 */
205
static int create_chat_message(void *data, int argc, char **argv, char **colName){
206
	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
207
	unsigned int storage_id = (unsigned int)atoi(argv[0]);
208 209 210 211 212 213 214 215

	// check if the message exists in the transient list, in which case we should return that one.
	LinphoneChatMessage* new_message = get_transient_message(cr, storage_id);
	if( new_message == NULL ){
		new_message = linphone_chat_room_create_message(cr, argv[4]);

		if(atoi(argv[3])==LinphoneChatMessageIncoming){
			new_message->dir=LinphoneChatMessageIncoming;
216
			linphone_chat_message_set_from(new_message,linphone_chat_room_get_peer_address(cr));
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
217
			new_message->to = NULL; /*will be filled at the end */
218 219
		} else {
			new_message->dir=LinphoneChatMessageOutgoing;
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
220
			new_message->from = NULL; /*will be filled at the end */
221
			linphone_chat_message_set_to(new_message,linphone_chat_room_get_peer_address(cr));
222
		}
223

224
		new_message->time = (time_t)atol(argv[9]);
225 226 227
		new_message->is_read=atoi(argv[6]);
		new_message->state=atoi(argv[7]);
		new_message->storage_id=storage_id;
228
		new_message->external_body_url= ms_strdup(argv[8]);
229
		new_message->appdata = ms_strdup(argv[10]);
230

231 232 233 234 235 236
		if (argv[11] != NULL) {
			int id = atoi(argv[11]);
			if (id >= 0) {
				fetch_content_from_database(cr->lc->db, new_message, id);
			}
		}
237
	}
238
	cr->messages_hist=bctbx_list_prepend(cr->messages_hist,new_message);
239 240 241 242 243 244 245

	return 0;
}

void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){
	char* errmsg=NULL;
	int ret;
246
	ret=sqlite3_exec(db,stmt,create_chat_message,cr,&errmsg);
247
	if(ret != SQLITE_OK) {
248
		ms_error("Error in creation: %s.", errmsg);
249 250 251 252
		sqlite3_free(errmsg);
	}
}

253
int linphone_sql_request(sqlite3* db,const char *stmt){
254 255 256 257
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
258
		ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg);
259 260
		sqlite3_free(errmsg);
	}
261
	return ret;
262 263 264 265 266 267 268 269
}

// 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) {
270
		ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.", errmsg);
271 272 273 274
		sqlite3_free(errmsg);
	}
}

275
static int linphone_chat_message_store_content(LinphoneChatMessage *msg) {
Simon Morlat's avatar
Simon Morlat committed
276
	LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room);
277 278 279
	int id = -1;
	if (lc->db) {
		LinphoneContent *content = msg->file_transfer_information;
280
		char *buf = sqlite3_mprintf("INSERT INTO content VALUES(NULL,%Q,%Q,%Q,%Q,%i,%Q,%lld,%Q);",
281 282 283 284 285
						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),
286 287 288
						NULL,
						(int64_t)linphone_content_get_key_size(content),
						linphone_content_get_key(content)
289
					);
290 291 292 293 294 295 296
		linphone_sql_request(lc->db, buf);
		sqlite3_free(buf);
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
	}
	return id;
}

297
unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){
Simon Morlat's avatar
Simon Morlat committed
298
	LinphoneCore *lc=linphone_chat_room_get_core(msg->chat_room);
299
	int id = 0;
300 301

	if (lc->db){
302
		int content_id = -1;
303 304 305
		char *peer;
		char *local_contact;
		char *buf;
306 307 308
		if (msg->file_transfer_information) {
			content_id = linphone_chat_message_store_content(msg);
		}
309 310 311

		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));
Sylvain Berfini's avatar
Sylvain Berfini committed
312
		buf = sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%lld,%Q,%i);",
313
						local_contact,
314 315 316 317 318 319 320
						peer,
						msg->dir,
						msg->message,
						"-1", /* use UTC field now */
						msg->is_read,
						msg->state,
						msg->external_body_url,
Simon Morlat's avatar
Simon Morlat committed
321
						(int64_t)msg->time,
322 323
						msg->appdata,
						content_id
324
					);
325 326 327 328 329 330 331 332 333
		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;
}

334 335
void linphone_chat_message_store_state(LinphoneChatMessage *msg){
	LinphoneCore *lc=msg->chat_room->lc;
336
	if (lc->db){
337
		char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (id = %u);",
338
								  msg->state,msg->storage_id);
339
		linphone_sql_request(lc->db,buf);
340 341 342 343
		sqlite3_free(buf);
	}
}

344 345
void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){
	LinphoneCore *lc=msg->chat_room->lc;
346
	if (lc->db){
347
		char *buf=sqlite3_mprintf("UPDATE history SET appdata=%Q WHERE id=%u;",
348 349 350 351 352 353
								  msg->appdata,msg->storage_id);
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
	}
}

354
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
Simon Morlat's avatar
Simon Morlat committed
355
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
356
	int read=1;
357 358
	char *peer;
	char *buf;
359 360

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

362 363
	// optimization: do not modify the database if no message is marked as unread
	if(linphone_chat_room_get_unread_messages_count(cr) == 0) return;
364

365 366
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf=sqlite3_mprintf("UPDATE history SET read=%i WHERE remoteContact = %Q;",
367 368 369 370
				   read,peer);
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);
371

372
	cr->unread_count = 0;
373 374 375
}

void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
Simon Morlat's avatar
Simon Morlat committed
376
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
377
	char *buf;
378 379 380

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

381
	buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%u;",msg->external_body_url,msg->storage_id);
382 383 384 385
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
}

386
static int linphone_chat_room_get_messages_count(LinphoneChatRoom *cr, bool_t unread_only){
Simon Morlat's avatar
Simon Morlat committed
387
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
388
	int numrows=0;
389 390 391 392
	char *peer;
	char *buf;
	sqlite3_stmt *selectStatement;
	int returnValue;
393 394

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

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

399 400 401
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;",peer,unread_only?"AND read = 0":"");
	returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL);
402 403 404 405 406 407 408 409
	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);
410

411 412 413
	/* no need to test the sign of cr->unread_count here
	 * because it has been tested above */
	if(unread_only) cr->unread_count = numrows;
414

415 416 417
	return numrows;
}

418 419 420 421 422 423 424 425
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);
}

426 427
void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=cr->lc;
428
	char *buf;
429 430 431

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

432
	buf=sqlite3_mprintf("DELETE FROM history WHERE id = %u;", msg->storage_id);
433 434
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
435

436 437 438
	/* 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;
439 440 441 442
}

void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
	LinphoneCore *lc=cr->lc;
443 444
	char *peer;
	char *buf;
445 446 447

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

448 449
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer);
450 451 452
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);
453

454
	if(cr->unread_count > 0) cr->unread_count = 0;
455 456
}

457
bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm){
Simon Morlat's avatar
Simon Morlat committed
458
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
459
	bctbx_list_t *ret;
460
	char *buf,*buf2;
461 462
	char *peer;
	uint64_t begin,end;
463
	int buf_max_size = 512;
464 465

	if (lc->db==NULL) return NULL;
466 467
	peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));

468
	cr->messages_hist = NULL;
469 470 471 472 473

	/*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);

474

475 476
	if (startm<0) startm=0;

477
	if ((endm>0&&endm>=startm) || (startm == 0 && endm == 0) ){
478 479 480
		buf2=ms_strdup_printf("%s LIMIT %i ",buf,endm+1-startm);
		ms_free(buf);
		buf = buf2;
481
	}else if(startm>0){
482 483 484 485
		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;
486 487 488
	}

	if (startm>0){
489 490 491
		buf2=ms_strdup_printf("%s OFFSET %i ",buf,startm);
		ms_free(buf);
		buf = buf2;
492
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
493

Simon Morlat's avatar
Simon Morlat committed
494 495 496
	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
497

498 499 500 501
	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));
	}
502
	ms_free(buf);
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519

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

520 521 522 523 524 525
	ret=cr->messages_hist;
	cr->messages_hist=NULL;
	ms_free(peer);
	return ret;
}

526
bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
527
	return linphone_chat_room_get_history_range(cr, 0, nb_message-1);
528 529
}

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
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;
}


577
static int migrate_messages_timestamp(void* data,int argc, char** argv, char** column_names) {
578 579 580
	time_t new_time = parse_time_from_db(argv[1]);
	if( new_time ){
		/* replace 'time' by -1 and set 'utc' to the timestamp */
581
		char *buf =	sqlite3_mprintf("UPDATE history SET utc=%lld,time='-1' WHERE id=%i;", (int64_t)new_time, atoi(argv[0]));
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
		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");

599
	ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1';", migrate_messages_timestamp, db, &errmsg);
600 601 602 603 604
	if( ret != SQLITE_OK ){
		ms_warning("Error migrating outgoing messages: %s.\n", errmsg);
		sqlite3_free(errmsg);
		linphone_sql_request(db, "ROLLBACK");
	} else {
605
		uint64_t end;
606
		linphone_sql_request(db, "COMMIT");
607
		end=ortp_get_cur_time_ms();
Simon Morlat's avatar
Simon Morlat committed
608
		ms_message("Migrated message timestamps to UTC in %lu ms",(unsigned long)(end-begin));
609 610 611 612 613 614 615 616 617 618 619 620 621
	}
}

void linphone_update_table(sqlite3* db) {
	char* errmsg=NULL;
	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 {
622
		ms_debug("Table history updated successfully for URL.");
623 624 625 626 627 628 629 630
	}

	// 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 {
631
		ms_debug("Table history updated successfully for UTC.");
632 633 634
		// migrate from old text-based timestamps to unix time-based timestamps
		linphone_migrate_timestamps(db);
	}
635 636 637 638 639 640 641

	// 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 {
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
		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.");
		}
668
	}
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
669

670 671 672 673 674 675 676 677 678 679 680 681 682 683
	// 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.");
		}
	}
684 685 686 687 688 689 690 691 692 693 694
}

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

695
static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){
Simon Morlat's avatar
Simon Morlat committed
696
	ms_warning("SQL statement '%s' took %" PRIu64 " microseconds", statement, (uint64_t)(duration / 1000LL) );
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
}

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

716 717 718
void linphone_core_message_storage_init(LinphoneCore *lc){
	int ret;
	const char *errmsg;
719
	sqlite3 *db = NULL;
720

721 722
	linphone_core_message_storage_close(lc);

723
	ret=_linphone_sqlite3_open(lc->chat_db_file,&db);
724 725 726 727
	if(ret != SQLITE_OK) {
		errmsg=sqlite3_errmsg(db);
		ms_error("Error in the opening: %s.\n", errmsg);
		sqlite3_close(db);
728
		return;
729
	}
730 731 732

	linphone_message_storage_activate_debug(db, lc->debug_storage);

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
	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;
}

754
void linphone_chat_message_store_state(LinphoneChatMessage *cr){
755 756
}

757
void linphone_chat_message_store_appdata(LinphoneChatMessage *msg){
758 759
}

760 761 762
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
}

763
bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
764 765 766
	return NULL;
}

767
LINPHONE_PUBLIC bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end){
768 769 770
	return NULL;
}

771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
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;
}

793 794 795 796
int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){
	return 0;
}

797
#endif