message_storage.c 13 KB
Newer Older
Margaux Clerc's avatar
Margaux Clerc committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/*
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"

#ifdef WIN32

static inline char *my_ctime_r(const time_t *t, char *buf){
	strcpy(buf,ctime(t));
	return buf;
}

#else
#define my_ctime_r ctime_r
#endif

#ifdef MSG_STORAGE_ENABLED

#include "sqlite3.h"

38 39 40 41 42 43 44 45 46 47 48 49 50
static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){
	MSList* transients = cr->transient_messages;
	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;
}

Simon Morlat's avatar
Simon Morlat committed
51
static void create_chat_message(char **argv, void *data){
Margaux Clerc's avatar
Margaux Clerc committed
52
	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
Simon Morlat's avatar
Simon Morlat committed
53
	LinphoneAddress *from;
54

55 56 57 58 59 60 61 62 63 64 65 66 67
	unsigned int storage_id = atoi(argv[0]);

	// 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;
			from=linphone_address_new(argv[2]);
		} else {
			new_message->dir=LinphoneChatMessageOutgoing;
			from=linphone_address_new(argv[1]);
Margaux Clerc's avatar
Margaux Clerc committed
68
		}
69 70 71
		linphone_chat_message_set_from(new_message,from);
		linphone_address_destroy(from);

72 73 74 75
		if( argv[9] != NULL ){
			new_message->time = (time_t)atol(argv[9]);
		} else {
			new_message->time = time(NULL);
Margaux Clerc's avatar
Margaux Clerc committed
76
		}
77

78 79 80 81
		new_message->is_read=atoi(argv[6]);
		new_message->state=atoi(argv[7]);
		new_message->storage_id=storage_id;
		new_message->external_body_url=argv[8]?ms_strdup(argv[8]):NULL;
Margaux Clerc's avatar
Margaux Clerc committed
82
	}
Simon Morlat's avatar
Simon Morlat committed
83
	cr->messages_hist=ms_list_prepend(cr->messages_hist,new_message);
Margaux Clerc's avatar
Margaux Clerc committed
84 85
}

86 87 88 89
// 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];
90
	linphone_core_get_or_create_chat_room(lc, address);
91
	return 0;
92 93
}

Margaux Clerc's avatar
Margaux Clerc committed
94
static int callback(void *data, int argc, char **argv, char **colName){
95 96
	create_chat_message(argv,data);
	return 0;
Margaux Clerc's avatar
Margaux Clerc committed
97 98
}

Simon Morlat's avatar
Simon Morlat committed
99
void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){
Margaux Clerc's avatar
Margaux Clerc committed
100
	char* errmsg=NULL;
Margaux Clerc's avatar
Margaux Clerc committed
101
	int ret;
Simon Morlat's avatar
Simon Morlat committed
102
	ret=sqlite3_exec(db,stmt,callback,cr,&errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
103
	if(ret != SQLITE_OK) {
104
		ms_error("Error in creation: %s.\n", errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
105
		sqlite3_free(errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
106 107 108 109
	}
}

void linphone_sql_request(sqlite3* db,const char *stmt){
Simon Morlat's avatar
Simon Morlat committed
110
	char* errmsg=NULL;
Margaux Clerc's avatar
Margaux Clerc committed
111
	int ret;
112
	ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
113
	if(ret != SQLITE_OK) {
Simon Morlat's avatar
Simon Morlat committed
114 115
		ms_error("linphone_sql_request: error sqlite3_exec(): %s.\n", errmsg);
		sqlite3_free(errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
116 117 118
	}
}

119 120 121 122 123 124 125 126 127 128 129
// 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) {
		ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.\n", errmsg);
		sqlite3_free(errmsg);
	}
}

130
unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){
Simon Morlat's avatar
Simon Morlat committed
131
	LinphoneCore *lc=linphone_chat_room_get_lc(msg->chat_room);
132
	int id=0;
133

Simon Morlat's avatar
Simon Morlat committed
134
	if (lc->db){
Margaux Clerc's avatar
Margaux Clerc committed
135
		char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room));
Simon Morlat's avatar
Simon Morlat committed
136
		char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg));
137 138 139 140 141 142 143 144 145 146
		char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i);",
						local_contact,
								  peer,
								  msg->dir,
								  msg->message,
								  "-1", /* use UTC field now */
								  msg->is_read,
								  msg->state,
								  msg->external_body_url,
								  msg->time);
Simon Morlat's avatar
Simon Morlat committed
147 148 149
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
		ms_free(local_contact);
Margaux Clerc's avatar
Margaux Clerc committed
150
		ms_free(peer);
151
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
Simon Morlat's avatar
Simon Morlat committed
152
	}
153
	return id;
Margaux Clerc's avatar
Margaux Clerc committed
154 155
}

Simon Morlat's avatar
Simon Morlat committed
156 157 158
void linphone_chat_message_store_state(LinphoneChatMessage *msg){
	LinphoneCore *lc=msg->chat_room->lc;
	if (lc->db){
159 160
		char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE message = %Q AND utc = %i;",
			msg->state,msg->message,msg->time);
Simon Morlat's avatar
Simon Morlat committed
161 162
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
163 164 165 166 167 168 169 170 171


	}

	if( msg->state == LinphoneChatMessageStateDelivered
			|| msg->state == LinphoneChatMessageStateNotDelivered ){
		// message is not transient anymore, we can remove it from our transient list:
		msg->chat_room->transient_messages = ms_list_remove(msg->chat_room->transient_messages, msg);
		linphone_chat_message_unref(msg);
Simon Morlat's avatar
Simon Morlat committed
172
	}
Margaux Clerc's avatar
Margaux Clerc committed
173 174
}

Simon Morlat's avatar
Simon Morlat committed
175
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
Margaux Clerc's avatar
Margaux Clerc committed
176
	LinphoneCore *lc=linphone_chat_room_get_lc(cr);
Simon Morlat's avatar
Simon Morlat committed
177
	int read=1;
178

Simon Morlat's avatar
Simon Morlat committed
179 180
	if (lc->db==NULL) return ;

Margaux Clerc's avatar
Margaux Clerc committed
181
	char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
182
	char *buf=sqlite3_mprintf("UPDATE history SET read=%i WHERE remoteContact = %Q;",
183
				   read,peer);
Margaux Clerc's avatar
Margaux Clerc committed
184
	linphone_sql_request(lc->db,buf);
Simon Morlat's avatar
Simon Morlat committed
185
	sqlite3_free(buf);
Margaux Clerc's avatar
Margaux Clerc committed
186 187 188
	ms_free(peer);
}

189 190
void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=linphone_chat_room_get_lc(cr);
191

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

194
	char *buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%i;",msg->external_body_url,msg->storage_id);
195 196 197 198
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
}

Margaux Clerc's avatar
Margaux Clerc committed
199 200 201
int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	LinphoneCore *lc=linphone_chat_room_get_lc(cr);
	int numrows=0;
202

Margaux Clerc's avatar
Margaux Clerc committed
203
	if (lc->db==NULL) return 0;
204

Margaux Clerc's avatar
Margaux Clerc committed
205
	char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
206
	char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q AND read = 0;",peer);
Margaux Clerc's avatar
Margaux Clerc committed
207 208 209 210 211 212 213 214 215 216 217
	sqlite3_stmt *selectStatement;
	int returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL);
	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);
	return numrows;
Margaux Clerc's avatar
Margaux Clerc committed
218 219
}

220 221
void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=cr->lc;
222

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

225
	char *buf=sqlite3_mprintf("DELETE FROM history WHERE id = %i;", msg->storage_id);
226 227 228 229
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
}

Simon Morlat's avatar
Simon Morlat committed
230 231
void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
	LinphoneCore *lc=cr->lc;
232

Simon Morlat's avatar
Simon Morlat committed
233
	if (lc->db==NULL) return ;
234

Margaux Clerc's avatar
Margaux Clerc committed
235
	char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
236
	char *buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer);
Margaux Clerc's avatar
Margaux Clerc committed
237
	linphone_sql_request(lc->db,buf);
Simon Morlat's avatar
Simon Morlat committed
238
	sqlite3_free(buf);
Margaux Clerc's avatar
Margaux Clerc committed
239
	ms_free(peer);
Margaux Clerc's avatar
Margaux Clerc committed
240 241
}

Simon Morlat's avatar
Simon Morlat committed
242
MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
Margaux Clerc's avatar
Margaux Clerc committed
243
	LinphoneCore *lc=linphone_chat_room_get_lc(cr);
Simon Morlat's avatar
Simon Morlat committed
244
	MSList *ret;
245 246
	char *buf;
	char *peer;
247

Simon Morlat's avatar
Simon Morlat committed
248
	if (lc->db==NULL) return NULL;
249
	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
Margaux Clerc's avatar
Margaux Clerc committed
250
	cr->messages_hist = NULL;
251
	if (nb_message > 0)
252
		buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message);
253
	else
254
		buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer);
Simon Morlat's avatar
Simon Morlat committed
255 256 257 258
	linphone_sql_request_message(lc->db,buf,cr);
	sqlite3_free(buf);
	ret=cr->messages_hist;
	cr->messages_hist=NULL;
Margaux Clerc's avatar
Margaux Clerc committed
259
	ms_free(peer);
Simon Morlat's avatar
Simon Morlat committed
260
	return ret;
Margaux Clerc's avatar
Margaux Clerc committed
261 262 263 264 265 266 267
}

void linphone_close_storage(sqlite3* db){
	sqlite3_close(db);
}

void linphone_create_table(sqlite3* db){
Margaux Clerc's avatar
Margaux Clerc committed
268
	char* errmsg=NULL;
Margaux Clerc's avatar
Margaux Clerc committed
269
	int ret;
270 271 272 273 274 275 276 277 278 279
	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"
						");",
280
			0,0,&errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
281
	if(ret != SQLITE_OK) {
282
		ms_error("Error in creation: %s.\n", errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
283
		sqlite3_free(errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
284 285 286
	}
}

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

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


static int migrate_messages(void* data,int argc, char** argv, char** column_names) {
	time_t new_time = parse_time_from_db(argv[1]);
	if( new_time ){
		/* replace 'time' by -1 and set 'utc' to the timestamp */
		char *buf =	sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i", new_time, atoi(argv[0]));
		if( buf) {
			linphone_sql_request((sqlite3*)data, buf);
			sqlite3_free(buf);
		}
	} else {
Simon Morlat's avatar
Simon Morlat committed
324
		ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]);
325 326 327 328 329 330 331 332 333 334
	}
	return 0;
}

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

	ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg);
	if( ret != SQLITE_OK ){
Guillaume BIENKOWSKI's avatar
Guillaume BIENKOWSKI committed
335
		ms_warning("Error migrating outgoing messages: %s.\n", errmsg);
336 337
		sqlite3_free(errmsg);
	} else {
Simon Morlat's avatar
Simon Morlat committed
338
		ms_message("Migrated message timestamps to UTC");
339 340 341
	}
}

342 343 344
void linphone_update_table(sqlite3* db) {
	char* errmsg=NULL;
	int ret;
345 346

	// for image url storage
347 348
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
Simon Morlat's avatar
Simon Morlat committed
349
		ms_message("Table already up to date: %s.", errmsg);
350 351
		sqlite3_free(errmsg);
	} else {
352 353 354 355 356 357
		ms_debug("Table updated successfully for URL.");
	}

	// for UTC timestamp storage
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg);
	if( ret != SQLITE_OK ){
Simon Morlat's avatar
Simon Morlat committed
358
		ms_message("Table already up to date: %s.", errmsg);
359 360 361
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table updated successfully for UTC.");
Simon Morlat's avatar
Simon Morlat committed
362 363
		// migrate from old text-based timestamps to unix time-based timestamps
		linphone_migrate_timestamps(db);
364 365 366
	}
}

367
void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
368
	char *buf;
369

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

Simon Morlat's avatar
Simon Morlat committed
376
void linphone_core_message_storage_init(LinphoneCore *lc){
Margaux Clerc's avatar
Margaux Clerc committed
377
	int ret;
Margaux Clerc's avatar
Margaux Clerc committed
378
	const char *errmsg;
Margaux Clerc's avatar
Margaux Clerc committed
379
	sqlite3 *db;
380 381 382

	linphone_core_message_storage_close(lc);

Simon Morlat's avatar
Simon Morlat committed
383
	ret=sqlite3_open(lc->chat_db_file,&db);
Margaux Clerc's avatar
Margaux Clerc committed
384
	if(ret != SQLITE_OK) {
Margaux Clerc's avatar
Margaux Clerc committed
385
		errmsg=sqlite3_errmsg(db);
386
		ms_error("Error in the opening: %s.\n", errmsg);
Margaux Clerc's avatar
Margaux Clerc committed
387 388 389
		sqlite3_close(db);
	}
	linphone_create_table(db);
390
	linphone_update_table(db);
Simon Morlat's avatar
Simon Morlat committed
391
	lc->db=db;
392 393 394

	// Create a chatroom for each contact in the chat history
	linphone_message_storage_init_chat_rooms(lc);
Simon Morlat's avatar
Simon Morlat committed
395 396 397 398 399 400 401
}

void linphone_core_message_storage_close(LinphoneCore *lc){
	if (lc->db){
		sqlite3_close(lc->db);
		lc->db=NULL;
	}
Margaux Clerc's avatar
Margaux Clerc committed
402
}
Simon Morlat's avatar
Simon Morlat committed
403

404
#else
Margaux Clerc's avatar
Margaux Clerc committed
405

406
unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){
407
	return 0;
Margaux Clerc's avatar
Margaux Clerc committed
408 409
}

Simon Morlat's avatar
Simon Morlat committed
410
void linphone_chat_message_store_state(LinphoneChatMessage *cr){
Margaux Clerc's avatar
Margaux Clerc committed
411 412
}

Simon Morlat's avatar
Simon Morlat committed
413
void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
Margaux Clerc's avatar
Margaux Clerc committed
414 415
}

Simon Morlat's avatar
Simon Morlat committed
416
MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
Margaux Clerc's avatar
Margaux Clerc committed
417 418 419
	return NULL;
}

420 421 422
void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
}

Simon Morlat's avatar
Simon Morlat committed
423 424 425
void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
}

426 427 428
void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
}

Simon Morlat's avatar
Simon Morlat committed
429
void linphone_core_message_storage_init(LinphoneCore *lc){
Margaux Clerc's avatar
Margaux Clerc committed
430
}
Simon Morlat's avatar
Simon Morlat committed
431 432 433 434

void linphone_core_message_storage_close(LinphoneCore *lc){
}

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

Margaux Clerc's avatar
Margaux Clerc committed
438 439 440 441
int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	return 0;
}

Simon Morlat's avatar
Simon Morlat committed
442
#endif