Merge branch 'dev_call_logs_sql' into dev_rtt

This commit is contained in:
Sylvain Berfini 2015-09-14 15:32:38 +02:00
commit e141a5ea38
12 changed files with 739 additions and 20 deletions

View file

@ -894,7 +894,7 @@ if test x$enable_msg_storage != xfalse; then
AC_CHECK_LIB(sqlite3, sqlite3_open, [SQLITE3_LIBS+=" -lsqlite3 "; found_sqlite=yes], [foo=bar])
fi
if test "$found_sqlite" = "yes"; then
SQLITE3_CFLAGS+="-DMSG_STORAGE_ENABLED"
SQLITE3_CFLAGS+=" -DMSG_STORAGE_ENABLED"
if test "$build_macos" = "yes" -o "$ios_found" = "yes"; then
SQLITE3_LIBS+=" -liconv"
fi
@ -912,6 +912,41 @@ fi
AM_CONDITIONAL(BUILD_MSG_STORAGE, test x$enable_msg_storage = xtrue)
AC_ARG_ENABLE(call-logs-storage,
[AS_HELP_STRING([--enable-call-logs-storage=[yes/no]], [Turn on compilation of call logs storage (default=auto)])],
[case "${enableval}" in
yes) enable_call_logs_storage=true ;;
no) enable_call_logs_storage=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-call-logs-storage) ;;
esac],
[enable_call_logs_storage=auto]
)
if test x$enable_call_logs_storage != xfalse; then
PKG_CHECK_MODULES(SQLITE3,[sqlite3 >= 3.6.0],[found_sqlite=yes],[found_sqlite=no])
if test "$found_sqlite" = "no"; then
dnl Check the lib presence in case the PKG-CONFIG version is not found
AC_CHECK_LIB(sqlite3, sqlite3_open, [SQLITE3_LIBS+=" -lsqlite3 "; found_sqlite=yes], [foo=bar])
fi
if test "$found_sqlite" = "yes"; then
SQLITE3_CFLAGS+=" -DCALL_LOGS_STORAGE_ENABLED"
if test "$build_macos" = "yes" -o "$ios_found" = "yes"; then
SQLITE3_LIBS+=" -liconv"
fi
enable_call_logs_storage=true
else
if test x$enable_call_logs_storage = xtrue; then
AC_MSG_ERROR([sqlite3, required for call logs storage, not found])
fi
enable_call_logs_storage=false
fi
AC_SUBST(SQLITE3_CFLAGS)
AC_SUBST(SQLITE3_LIBS)
fi
AM_CONDITIONAL(BUILD_CALL_LOGS_STORAGE, test x$enable_call_logs_storage = xtrue)
PKG_CHECK_MODULES(BELLESIP, [belle-sip >= 1.4.0])
SIPSTACK_CFLAGS="$BELLESIP_CFLAGS"
@ -1065,6 +1100,7 @@ printf "* %-30s %s\n" "Account assistant" $build_wizard
printf "* %-30s %s\n" "Console interface" $console_ui
printf "* %-30s %s\n" "Tools" $build_tools
printf "* %-30s %s\n" "Message storage" $enable_msg_storage
printf "* %-30s %s\n" "Call logs storage" $enable_call_logs_storage
printf "* %-30s %s\n" "IM encryption" $lime
printf "* %-30s %s\n" "uPnP support" $build_upnp
printf "* %-30s %s\n" "LDAP support" $enable_ldap

View file

@ -22,6 +22,20 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <time.h>
#include "private.h"
#ifdef CALL_LOGS_STORAGE_ENABLED
#ifndef _WIN32
#if !defined(ANDROID) && !defined(__QNXNTO__)
# include <langinfo.h>
# include <iconv.h>
# include <string.h>
#endif
#else
#include <Windows.h>
#endif
#define MAX_PATH_SIZE 1024
#include "sqlite3.h"
#endif
/*******************************************************************************
* Internal functions *
@ -260,7 +274,7 @@ void linphone_call_log_unref(LinphoneCallLog *cl) {
* Constructor and destructor functions *
******************************************************************************/
static void _linphone_call_log_destroy(LinphoneCallLog *cl){
static void _linphone_call_log_destroy(LinphoneCallLog *cl) {
if (cl->from!=NULL) linphone_address_destroy(cl->from);
if (cl->to!=NULL) linphone_address_destroy(cl->to);
if (cl->refkey!=NULL) ms_free(cl->refkey);
@ -269,7 +283,7 @@ static void _linphone_call_log_destroy(LinphoneCallLog *cl){
if (cl->reporting.reports[LINPHONE_CALL_STATS_VIDEO]!=NULL) linphone_reporting_destroy(cl->reporting.reports[LINPHONE_CALL_STATS_VIDEO]);
}
LinphoneCallLog * linphone_call_log_new(LinphoneCallDir dir, LinphoneAddress *from, LinphoneAddress *to){
LinphoneCallLog * linphone_call_log_new(LinphoneCallDir dir, LinphoneAddress *from, LinphoneAddress *to) {
LinphoneCallLog *cl=belle_sip_object_new(LinphoneCallLog);
cl->dir=dir;
cl->start_date_time=time(NULL);
@ -278,6 +292,7 @@ LinphoneCallLog * linphone_call_log_new(LinphoneCallDir dir, LinphoneAddress *fr
cl->to=to;
cl->status=LinphoneCallAborted; /*default status*/
cl->quality=-1;
cl->storage_id=0;
cl->reporting.reports[LINPHONE_CALL_STATS_AUDIO]=linphone_reporting_new();
cl->reporting.reports[LINPHONE_CALL_STATS_VIDEO]=linphone_reporting_new();
@ -298,3 +313,291 @@ BELLE_SIP_INSTANCIATE_VPTR(LinphoneCallLog, belle_sip_object_t,
NULL, // marshal
FALSE
);
/*******************************************************************************
* SQL storage related functions *
******************************************************************************/
#ifdef CALL_LOGS_STORAGE_ENABLED
static void linphone_create_table(sqlite3* db) {
char* errmsg=NULL;
int ret;
ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS call_history ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"caller TEXT NOT NULL," // Can't name a field "from"...
"callee TEXT NOT NULL,"
"direction INTEGER,"
"duration INTEGER,"
"start_time TEXT NOT NULL,"
"connected_time TEXT NOT NULL,"
"status INTEGER,"
"videoEnabled INTEGER,"
"quality REAL"
");",
0,0,&errmsg);
if(ret != SQLITE_OK) {
ms_error("Error in creation: %s.\n", errmsg);
sqlite3_free(errmsg);
}
}
static int _linphone_sqlite3_open(const char *db_file, sqlite3 **db) {
#if defined(ANDROID) || defined(__QNXNTO__)
return sqlite3_open(db_file, db);
#elif defined(_WIN32)
int ret;
wchar_t db_file_utf16[MAX_PATH_SIZE];
ret = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, db_file, -1, db_file_utf16, MAX_PATH_SIZE);
if(ret == 0) db_file_utf16[0] = '\0';
return sqlite3_open16(db_file_utf16, db);
#else
char db_file_locale[MAX_PATH_SIZE] = {'\0'};
char db_file_utf8[MAX_PATH_SIZE] = "";
char *inbuf=db_file_locale, *outbuf=db_file_utf8;
size_t inbyteleft = MAX_PATH_SIZE, outbyteleft = MAX_PATH_SIZE;
iconv_t cb;
strncpy(db_file_locale, db_file, 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);
}
return sqlite3_open(db_file_utf8, db);
#endif
}
void linphone_core_call_log_storage_init(LinphoneCore *lc) {
int ret;
const char *errmsg;
sqlite3 *db;
linphone_core_call_log_storage_close(lc);
ret=_linphone_sqlite3_open(lc->logs_db_file, &db);
if(ret != SQLITE_OK) {
errmsg = sqlite3_errmsg(db);
ms_error("Error in the opening: %s.\n", errmsg);
sqlite3_close(db);
return;
}
linphone_create_table(db);
lc->logs_db = db;
// Load the existing call logs
linphone_core_get_call_history(lc);
}
void linphone_core_call_log_storage_close(LinphoneCore *lc) {
if (lc->logs_db){
sqlite3_close(lc->logs_db);
lc->logs_db = NULL;
}
}
/* DB layout:
* | 0 | storage_id
* | 1 | from
* | 2 | to
* | 3 | direction flag
* | 4 | duration
* | 5 | start date time (time_t)
* | 6 | connected date time (time_t)
* | 7 | status
* | 8 | video enabled (1 or 0)
* | 9 | quality
*/
static int create_call_log(void *data, int argc, char **argv, char **colName) {
MSList **list = (MSList **)data;
LinphoneAddress *from;
LinphoneAddress *to;
LinphoneCallDir dir;
LinphoneCallLog *log;
unsigned int storage_id = atoi(argv[0]);
from = linphone_address_new(argv[1]);
to = linphone_address_new(argv[2]);
dir = (LinphoneCallDir) atoi(argv[3]);
log = linphone_call_log_new(dir, from, to);
log->storage_id = storage_id;
log->duration = atoi(argv[4]);
log->start_date_time = (time_t)atol(argv[5]);
log->connected_date_time = (time_t)atol(argv[6]);
log->status = (LinphoneCallStatus) atoi(argv[7]);
log->video_enabled = atoi(argv[8]) == 1;
log->quality = atof(argv[9]);
*list = ms_list_append(*list, log);
return 0;
}
void linphone_sql_request_call_log(sqlite3 *db, const char *stmt, MSList **list) {
char* errmsg = NULL;
int ret;
ret = sqlite3_exec(db, stmt, create_call_log, list, &errmsg);
if (ret != SQLITE_OK) {
ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg);
sqlite3_free(errmsg);
}
}
int linphone_sql_request_generic(sqlite3* db, const char *stmt) {
char* errmsg = NULL;
int ret;
ret = sqlite3_exec(db, stmt, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg);
sqlite3_free(errmsg);
}
return ret;
}
void linphone_core_store_call_log(LinphoneCore *lc, LinphoneCallLog *log) {
if (lc && lc->logs_db){
char *from, *to;
char *buf;
from = linphone_address_as_string(log->from);
to = linphone_address_as_string(log->to);
buf = sqlite3_mprintf("INSERT INTO call_history VALUES(NULL,%Q,%Q,%i,%i,%lld,%lld,%i,%i,%f);",
from,
to,
log->dir,
log->duration,
(int64_t)log->start_date_time,
(int64_t)log->connected_date_time,
log->status,
log->video_enabled ? 1 : 0,
log->quality
);
linphone_sql_request_generic(lc->logs_db, buf);
sqlite3_free(buf);
ms_free(from);
ms_free(to);
sqlite3_last_insert_rowid(lc->logs_db);
}
if (lc) {
lc->call_logs = ms_list_prepend(lc->call_logs, linphone_call_log_ref(log));
}
}
const MSList *linphone_core_get_call_history(LinphoneCore *lc) {
char *buf;
uint64_t begin,end;
if (!lc || lc->logs_db == NULL) return NULL;
lc->call_logs = ms_list_free_with_data(lc->call_logs, (void (*)(void*))linphone_call_log_unref);
buf = sqlite3_mprintf("SELECT * FROM call_history ORDER BY id DESC LIMIT %i", lc->max_call_logs);
begin = ortp_get_cur_time_ms();
linphone_sql_request_call_log(lc->logs_db, buf, &lc->call_logs);
end = ortp_get_cur_time_ms();
ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin));
sqlite3_free(buf);
return lc->call_logs;
}
void linphone_core_delete_call_history(LinphoneCore *lc) {
char *buf;
if (!lc || lc->logs_db == NULL) return ;
buf = sqlite3_mprintf("DELETE FROM call_history");
linphone_sql_request_generic(lc->logs_db, buf);
sqlite3_free(buf);
}
void linphone_core_delete_call_log(LinphoneCore *lc, LinphoneCallLog *log) {
char *buf;
if (!lc || lc->logs_db == NULL) return ;
buf = sqlite3_mprintf("DELETE FROM call_history WHERE id = %i", log->storage_id);
linphone_sql_request_generic(lc->logs_db, buf);
sqlite3_free(buf);
}
int linphone_core_get_call_history_size(LinphoneCore *lc) {
int numrows = 0;
char *buf;
sqlite3_stmt *selectStatement;
int returnValue;
if (!lc || lc->logs_db == NULL) return 0;
buf = sqlite3_mprintf("SELECT count(*) FROM call_history");
returnValue = sqlite3_prepare_v2(lc->logs_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);
return numrows;
}
MSList * linphone_core_get_call_history_for_address(LinphoneCore *lc, const LinphoneAddress *addr) {
char *buf;
char *sipAddress;
uint64_t begin,end;
MSList *result = NULL;
if (!lc || lc->logs_db == NULL || addr == NULL) return NULL;
/*since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf*/
sipAddress = linphone_address_as_string_uri_only(addr);
buf = sqlite3_mprintf("SELECT * FROM call_history WHERE caller LIKE '%%%q%%' OR callee LIKE '%%%q%%' ORDER BY id DESC", sipAddress, sipAddress); // The '%%%q%%' takes care of the eventual presence of a display name
begin = ortp_get_cur_time_ms();
linphone_sql_request_call_log(lc->logs_db, buf, &result);
end = ortp_get_cur_time_ms();
ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin));
sqlite3_free(buf);
ms_free(sipAddress);
return result;
}
#else
void linphone_core_call_log_storage_init(LinphoneCore *lc) {
}
void linphone_core_call_log_storage_close(LinphoneCore *lc) {
}
void linphone_core_store_call_log(LinphoneCore *lc, LinphoneCallLog *log) {
}
const MSList *linphone_core_get_call_history(LinphoneCore *lc) {
return NULL;
}
void linphone_core_delete_call_history(LinphoneCore *lc) {
}
void linphone_core_delete_call_log(LinphoneCore *lc, LinphoneCallLog *log) {
}
int linphone_core_get_call_history_size(LinphoneCore *lc) {
return 0;
}
MSList * linphone_core_get_call_history_for_address(LinphoneCore *lc, const LinphoneAddress *addr) {
return NULL;
}
#endif

View file

@ -3955,6 +3955,9 @@ void linphone_call_log_completed(LinphoneCall *call){
linphone_core_notify_display_status(lc,info);
ms_free(info);
}
#ifdef CALL_LOGS_STORAGE_ENABLED
linphone_core_store_call_log(lc, call->log);
#else
lc->call_logs=ms_list_prepend(lc->call_logs,linphone_call_log_ref(call->log));
if (ms_list_size(lc->call_logs)>lc->max_call_logs){
MSList *elem,*prevelem=NULL;
@ -3966,8 +3969,9 @@ void linphone_call_log_completed(LinphoneCall *call){
linphone_call_log_unref((LinphoneCallLog*)elem->data);
lc->call_logs=ms_list_remove_link(lc->call_logs,elem);
}
linphone_core_notify_call_log_updated(lc,call->log);
call_logs_write_to_config_file(lc);
#endif
linphone_core_notify_call_log_updated(lc,call->log);
}
/**

View file

@ -1294,7 +1294,10 @@ static void ui_config_read(LinphoneCore *lc)
linphone_core_add_friend(lc,lf);
linphone_friend_unref(lf);
}
#ifndef CALL_LOGS_STORAGE_ENABLED
call_logs_read_from_config_file(lc);
#endif
}
/*
@ -4935,15 +4938,35 @@ LinphoneFirewallPolicy linphone_core_get_firewall_policy(const LinphoneCore *lc)
* Call log related functions *
******************************************************************************/
const MSList * linphone_core_get_call_logs(LinphoneCore *lc){
void linphone_core_set_call_logs_database_path(LinphoneCore *lc, const char *path) {
if (lc->logs_db_file){
ms_free(lc->logs_db_file);
lc->logs_db_file = NULL;
}
if (path) {
lc->logs_db_file = ms_strdup(path);
linphone_core_call_log_storage_init(lc);
linphone_core_migrate_logs_from_rc_to_db(lc);
}
}
const MSList* linphone_core_get_call_logs(LinphoneCore *lc) {
#ifdef CALL_LOGS_STORAGE_ENABLED
linphone_core_get_call_history(lc);
#endif
return lc->call_logs;
}
void linphone_core_clear_call_logs(LinphoneCore *lc){
void linphone_core_clear_call_logs(LinphoneCore *lc) {
lc->missed_calls=0;
ms_list_for_each(lc->call_logs,(void (*)(void*))linphone_call_log_unref);
lc->call_logs=ms_list_free(lc->call_logs);
#ifdef CALL_LOGS_STORAGE_ENABLED
linphone_core_delete_call_history(lc);
#else
ms_list_for_each(lc->call_logs, (void (*)(void*))linphone_call_log_unref);
lc->call_logs = ms_list_free(lc->call_logs);
call_logs_write_to_config_file(lc);
#endif
}
int linphone_core_get_missed_calls_count(LinphoneCore *lc) {
@ -4954,13 +4977,80 @@ void linphone_core_reset_missed_calls_count(LinphoneCore *lc) {
lc->missed_calls=0;
}
void linphone_core_remove_call_log(LinphoneCore *lc, LinphoneCallLog *cl){
void linphone_core_remove_call_log(LinphoneCore *lc, LinphoneCallLog *cl) {
#ifdef CALL_LOGS_STORAGE_ENABLED
linphone_core_delete_call_log(lc, cl);
#else
lc->call_logs = ms_list_remove(lc->call_logs, cl);
call_logs_write_to_config_file(lc);
linphone_call_log_unref(cl);
#endif
}
void linphone_core_migrate_logs_from_rc_to_db(LinphoneCore *lc) {
MSList *logs_to_migrate = NULL;
LpConfig *lpc = NULL;
int original_logs_count, migrated_logs_count;
int i;
#ifndef CALL_LOGS_STORAGE_ENABLED
ms_warning("linphone has been compiled without sqlite, can't migrate call logs");
return;
#endif
if (!lc) {
return;
}
lpc = linphone_core_get_config(lc);
if (!lpc) {
ms_warning("this core has been started without a rc file, nothing to migrate");
return;
}
if (lp_config_get_int(lpc, "misc", "call_logs_migration_done", 0) == 1) {
ms_warning("the call logs migration has already been done, skipping...");
return;
}
// This is because there must have been a call previously to linphone_core_call_log_storage_init
lc->call_logs = ms_list_free_with_data(lc->call_logs, (void (*)(void*))linphone_call_log_unref);
call_logs_read_from_config_file(lc);
if (!lc->call_logs) {
ms_warning("nothing to migrate, skipping...");
return;
}
logs_to_migrate = lc->call_logs;
lc->call_logs = NULL;
// We can't use ms_list_for_each because logs_to_migrate are listed in the wrong order (latest first), and we want to store the logs latest last
for (i = ms_list_size(logs_to_migrate) - 1; i >= 0; i--) {
LinphoneCallLog *log = (LinphoneCallLog *) ms_list_nth_data(logs_to_migrate, i);
linphone_core_store_call_log(lc, log);
}
original_logs_count = ms_list_size(logs_to_migrate);
migrated_logs_count = ms_list_size(lc->call_logs);
if (original_logs_count == migrated_logs_count) {
int i = 0;
ms_debug("call logs migration successful: %i logs migrated", ms_list_size(lc->call_logs));
lp_config_set_int(lpc, "misc", "call_logs_migration_done", 1);
for (; i < original_logs_count; i++) {
char logsection[32];
snprintf(logsection, sizeof(logsection), "call_log_%i", i);
lp_config_clean_section(lpc, logsection);
}
} else {
ms_error("not as many logs saved in db has logs read from rc (%i in rc against %i in db)!", original_logs_count, migrated_logs_count);
}
ms_list_free_with_data(logs_to_migrate, (void (*)(void*))linphone_call_log_unref);
}
/*******************************************************************************
* Video related functions *
******************************************************************************/
static void toggle_video_preview(LinphoneCore *lc, bool_t val){
@ -6176,6 +6266,7 @@ static void linphone_core_uninit(LinphoneCore *lc)
linphone_core_free_payload_types(lc);
if (lc->supported_formats) ms_free(lc->supported_formats);
linphone_core_message_storage_close(lc);
linphone_core_call_log_storage_close(lc);
ms_exit();
linphone_core_set_state(lc,LinphoneGlobalOff,"Off");
linphone_core_deactivate_log_serialization_if_needed();

View file

@ -3208,6 +3208,14 @@ LINPHONE_PUBLIC void linphone_core_set_rtp_no_xmit_on_audio_mute(LinphoneCore *l
**/
LINPHONE_PUBLIC const MSList * linphone_core_get_call_logs(LinphoneCore *lc);
/**
* Get the list of call logs (past calls) that matches the given #LinphoneAddress.
* At the contrary of linphone_core_get_call_logs, it is your responsability to unref the logs and free this list once you are done using it.
* @param[in] lc LinphoneCore object
* @return \mslist{LinphoneCallLog}
**/
LINPHONE_PUBLIC MSList * linphone_core_get_call_history_for_address(LinphoneCore *lc, const LinphoneAddress *addr);
/**
* Erase the call log.
* @param[in] lc LinphoneCore object
@ -3236,6 +3244,22 @@ LINPHONE_PUBLIC void linphone_core_reset_missed_calls_count(LinphoneCore *lc);
**/
LINPHONE_PUBLIC void linphone_core_remove_call_log(LinphoneCore *lc, LinphoneCallLog *call_log);
/**
* Sets the database filename where call logs will be stored.
* If the file does not exist, it will be created.
* @ingroup initializing
* @param lc the linphone core
* @param path filesystem path
**/
LINPHONE_PUBLIC void linphone_core_set_call_logs_database_path(LinphoneCore *lc, const char *path);
/**
* Migrates the call logs from the linphonerc to the database if not done yet
* @ingroup initializing
* @param lc the linphone core
**/
LINPHONE_PUBLIC void linphone_core_migrate_logs_from_rc_to_db(LinphoneCore *lc);
/**
* @}
**/

View file

@ -433,11 +433,6 @@ MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
return linphone_chat_room_get_history_range(cr, 0, nb_message-1);
}
void linphone_close_storage(sqlite3* db){
sqlite3_close(db);
}
void linphone_create_table(sqlite3* db){
char* errmsg=NULL;
int ret;

View file

@ -172,6 +172,7 @@ struct _LinphoneCallLog{
char* call_id; /**unique id of a call*/
struct _LinphoneQualityReporting reporting;
bool_t video_enabled;
unsigned int storage_id;
};
BELLE_SIP_DECLARE_VPTR(LinphoneCallLog);
@ -850,6 +851,10 @@ struct _LinphoneCore
#ifdef MSG_STORAGE_ENABLED
sqlite3 *db;
bool_t debug_storage;
#endif
char *logs_db_file;
#ifdef CALL_LOGS_STORAGE_ENABLED
sqlite3 *logs_db;
#endif
#ifdef BUILD_UPNP
UpnpContext *upnp;
@ -968,6 +973,13 @@ void _linphone_core_codec_config_write(LinphoneCore *lc);
#endif
void call_logs_read_from_config_file(LinphoneCore *lc);
void call_logs_write_to_config_file(LinphoneCore *lc);
void linphone_core_call_log_storage_init(LinphoneCore *lc);
void linphone_core_call_log_storage_close(LinphoneCore *lc);
void linphone_core_store_call_log(LinphoneCore *lc, LinphoneCallLog *log);
const MSList *linphone_core_get_call_history(LinphoneCore *lc);
void linphone_core_delete_call_history(LinphoneCore *lc);
void linphone_core_delete_call_log(LinphoneCore *lc, LinphoneCallLog *log);
int linphone_core_get_call_history_size(LinphoneCore *lc);
int linphone_core_get_edge_bw(LinphoneCore *lc);
int linphone_core_get_edge_ptime(LinphoneCore *lc);

View file

@ -19,6 +19,34 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "linphone.h"
#define CONFIG_FILE ".linphone-call-history.db"
char *linphone_gtk_call_logs_storage_get_db_file(const char *filename){
const int path_max=1024;
char *db_file=NULL;
db_file=(char *)g_malloc(path_max*sizeof(char));
if (filename==NULL) filename=CONFIG_FILE;
/*try accessing a local file first if exists*/
if (access(CONFIG_FILE,F_OK)==0){
snprintf(db_file,path_max,"%s",filename);
}else{
#ifdef WIN32
const char *appdata=getenv("APPDATA");
if (appdata){
snprintf(db_file,path_max,"%s\\%s",appdata,LINPHONE_CONFIG_DIR);
CreateDirectory(db_file,NULL);
snprintf(db_file,path_max,"%s\\%s\\%s",appdata,LINPHONE_CONFIG_DIR,filename);
}
#else
const char *home=getenv("HOME");
if (home==NULL) home=".";
snprintf(db_file,path_max,"%s/%s",home,filename);
#endif
}
return db_file;
}
static void fill_renderers(GtkTreeView *v){
GtkTreeViewColumn *c;
GtkCellRenderer *r;

View file

@ -101,6 +101,7 @@ LINPHONE_PUBLIC GtkWidget *linphone_gtk_get_widget(GtkWidget *window, const char
LINPHONE_PUBLIC GtkWidget *linphone_gtk_create_widget(const char* widget_name);
char *linphone_gtk_message_storage_get_db_file(const char *filename);
char *linphone_gtk_call_logs_storage_get_db_file(const char *filename);
LINPHONE_PUBLIC void linphone_gtk_show_assistant(GtkWidget* parent);
LINPHONE_PUBLIC void linphone_gtk_close_assistant(void);

View file

@ -259,7 +259,7 @@ gboolean linphone_gtk_get_audio_assistant_option(void){
}
static void linphone_gtk_init_liblinphone(const char *config_file,
const char *factory_config_file, const char *db_file) {
const char *factory_config_file, const char *chat_messages_db_file, const char *call_logs_db_file) {
LinphoneCoreVTable vtable={0};
gchar *secrets_file=linphone_gtk_get_config_file(SECRETS_FILE);
gchar *user_certificates_dir=linphone_gtk_get_config_file(CERTIFICATES_PATH);
@ -309,7 +309,8 @@ static void linphone_gtk_init_liblinphone(const char *config_file,
_linphone_gtk_enable_video(FALSE);
linphone_gtk_set_ui_config_int("videoselfview",0);
}
if (db_file) linphone_core_set_chat_database_path(the_core,db_file);
if (chat_messages_db_file) linphone_core_set_chat_database_path(the_core,chat_messages_db_file);
if (call_logs_db_file) linphone_core_set_call_logs_database_path(the_core, call_logs_db_file);
}
LinphoneCore *linphone_gtk_get_core(void){
@ -1899,7 +1900,9 @@ static void linphone_gtk_init_main_window(){
linphone_gtk_show_friends();
linphone_core_reset_missed_calls_count(linphone_gtk_get_core());
main_window=linphone_gtk_get_main_window();
#ifndef CALL_LOGS_STORAGE_ENABLED
linphone_gtk_call_log_update(main_window);
#endif
linphone_gtk_update_call_buttons (NULL);
g_object_set_data(G_OBJECT(main_window),"keypad",NULL);
@ -2063,7 +2066,7 @@ int main(int argc, char *argv[]){
GdkPixbuf *pbuf;
const char *app_name="Linphone";
LpConfig *factory;
char *db_file;
char *chat_messages_db_file, *call_logs_db_file;
GError *error=NULL;
const char *tmp;
@ -2201,9 +2204,15 @@ core_start:
linphone_gtk_create_log_window();
linphone_core_enable_logs_with_cb(linphone_gtk_log_handler);
db_file=linphone_gtk_message_storage_get_db_file(NULL);
linphone_gtk_init_liblinphone(config_file, factory_config_file, db_file);
g_free(db_file);
chat_messages_db_file=linphone_gtk_message_storage_get_db_file(NULL);
call_logs_db_file = linphone_gtk_call_logs_storage_get_db_file(NULL);
linphone_gtk_init_liblinphone(config_file, factory_config_file, chat_messages_db_file, call_logs_db_file);
g_free(chat_messages_db_file);
g_free(call_logs_db_file);
#ifdef CALL_LOGS_STORAGE_ENABLED
linphone_gtk_call_log_update(the_ui);
#endif
/* do not lower timeouts under 30 ms because it exhibits a bug on gtk+/win32, with cpu running 20% all the time...*/
gtk_timeout_add(30,(GtkFunction)linphone_gtk_iterate,(gpointer)linphone_gtk_get_core());

View file

@ -4950,6 +4950,109 @@ static void call_with_network_switch(void){
static void call_with_network_switch_and_ice(void){
_call_with_network_switch(TRUE);
}
#endif
#ifdef CALL_LOGS_STORAGE_ENABLED
static void call_logs_migrate() {
LinphoneCoreManager* laure = linphone_core_manager_new("laure_call_logs_rc");
char *logs_db = create_filepath(bc_tester_get_writable_dir_prefix(), "call_logs", "db");
int i = 0;
int incoming_count = 0, outgoing_count = 0, missed_count = 0, aborted_count = 0, decline_count = 0, video_enabled_count = 0;
call_logs_read_from_config_file(laure->lc);
BC_ASSERT_TRUE(ms_list_size(laure->lc->call_logs) == 10);
linphone_core_set_call_logs_database_path(laure->lc, logs_db);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(laure->lc) == 10);
for (; i < ms_list_size(laure->lc->call_logs); i++) {
LinphoneCallLog *log = ms_list_nth_data(laure->lc->call_logs, i);
LinphoneCallStatus state = linphone_call_log_get_status(log);
LinphoneCallDir direction = linphone_call_log_get_dir(log);
if (state == LinphoneCallAborted) {
aborted_count += 1;
} else if (state == LinphoneCallMissed) {
missed_count += 1;
} else if (state == LinphoneCallDeclined) {
decline_count += 1;
}
if (direction == LinphoneCallOutgoing) {
outgoing_count += 1;
} else {
incoming_count += 1;
}
if (linphone_call_log_video_enabled(log)) {
video_enabled_count += 1;
}
}
BC_ASSERT_TRUE(incoming_count == 5);
BC_ASSERT_TRUE(outgoing_count == 5);
BC_ASSERT_TRUE(missed_count == 1);
BC_ASSERT_TRUE(aborted_count == 3);
BC_ASSERT_TRUE(decline_count == 2);
BC_ASSERT_TRUE(video_enabled_count == 3);
laure->lc->call_logs = ms_list_free_with_data(laure->lc->call_logs, (void (*)(void*))linphone_call_log_unref);
call_logs_read_from_config_file(laure->lc);
BC_ASSERT_TRUE(ms_list_size(laure->lc->call_logs) == 0);
remove(logs_db);
ms_free(logs_db);
linphone_core_manager_destroy(laure);
}
static void call_logs_sqlite_storage() {
LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc");
char *logs_db = create_filepath(bc_tester_get_writable_dir_prefix(), "call_logs", "db");
MSList *logs = NULL;
LinphoneAddress *laure = NULL;
linphone_core_set_call_logs_database_path(marie->lc, logs_db);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(marie->lc) == 0);
BC_ASSERT_TRUE(call(marie, pauline));
wait_for_until(marie->lc, pauline->lc, NULL, 5, 1000);
end_call(marie, pauline);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(marie->lc) == 1);
logs = linphone_core_get_call_history_for_address(marie->lc, linphone_proxy_config_get_identity_address(linphone_core_get_default_proxy_config(pauline->lc)));
BC_ASSERT_TRUE(ms_list_size(logs) == 1);
ms_list_free_with_data(logs, (void (*)(void*))linphone_call_log_unref);
laure = linphone_address_new("\"Laure\" <sip:laure@sip.example.org>");
logs = linphone_core_get_call_history_for_address(marie->lc, laure);
BC_ASSERT_TRUE(ms_list_size(logs) == 0);
ms_free(laure);
logs = linphone_core_get_call_history_for_address(marie->lc, linphone_proxy_config_get_identity_address(linphone_core_get_default_proxy_config(pauline->lc)));
BC_ASSERT_TRUE(ms_list_size(logs) == 1);
linphone_core_delete_call_log(marie->lc, (LinphoneCallLog *)ms_list_nth_data(logs, 0));
ms_list_free_with_data(logs, (void (*)(void*))linphone_call_log_unref);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(marie->lc) == 0);
BC_ASSERT_TRUE(call(marie, pauline));
wait_for_until(marie->lc, pauline->lc, NULL, 5, 1000);
end_call(marie, pauline);
BC_ASSERT_TRUE(call(marie, pauline));
wait_for_until(marie->lc, pauline->lc, NULL, 5, 1000);
end_call(marie, pauline);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(marie->lc) == 2);
linphone_core_delete_call_history(marie->lc);
BC_ASSERT_TRUE(linphone_core_get_call_history_size(marie->lc) == 0);
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
remove(logs_db);
ms_free(logs_db);
}
#endif
test_t call_tests[] = {
@ -5082,6 +5185,10 @@ test_t call_tests[] = {
{ "Call with RTP IO mode", call_with_rtp_io_mode },
{ "Call with generic NACK RTCP feedback", call_with_generic_nack_rtcp_feedback },
{ "Call with complex late offering", call_with_complex_late_offering },
#ifdef CALL_LOGS_STORAGE_ENABLED
{ "Call log storage migration from rc to db", call_logs_migrate },
{ "Call log storage in sqlite database", call_logs_sqlite_storage },
#endif
{ "Call with custom RTP Modifier", call_with_custom_rtp_modifier },
{ "Call paused resumed with custom RTP Modifier", call_paused_resumed_with_custom_rtp_modifier },
{ "Call record with custom RTP Modifier", call_record_with_custom_rtp_modifier },

View file

@ -0,0 +1,109 @@
[call_log_0]
dir=0
status=0
from=sip:pauline@sip.linphone.org
to=sip:laure@sip.linphone.org
start_date_time=1441738272
duration=3
quality=5.000000
video_enabled=0
call_id=o6mx-QSdKJ
[call_log_1]
dir=0
status=1
from=sip:pauline@sip.linphone.org
to=sip:laure@sip.linphone.org
start_date_time=1441738266
duration=0
quality=-1.000000
video_enabled=0
call_id=wnXDtOhURa
[call_log_2]
dir=1
status=2
from=sip:laure@sip.linphone.org
to=sip:pauline@sip.linphone.org
start_date_time=1441738258
duration=0
quality=-1.000000
video_enabled=0
call_id=81ZldO6dlk
[call_log_3]
dir=1
status=1
from=sip:laure@sip.linphone.org
to=sip:pauline@sip.linphone.org
start_date_time=1441738250
duration=0
quality=-1.000000
video_enabled=0
call_id=3yq7TlwS8Q
[call_log_4]
dir=1
status=0
from=sip:laure@sip.linphone.org
to=sip:pauline@sip.linphone.org
start_date_time=1441738243
duration=0
quality=-1.000000
video_enabled=1
call_id=EmoqEyeLbZ
[call_log_5]
dir=0
status=1
from=sip:pauline@sip.linphone.org
to=sip:laure@sip.linphone.org
start_date_time=1441738234
duration=0
quality=-1.000000
video_enabled=0
call_id=xl29jqHZ2N
[call_log_6]
dir=1
status=0
from=sip:laure@sip.linphone.org
to=sip:pauline@sip.linphone.org
start_date_time=1441738215
duration=6
quality=4.898808
video_enabled=1
call_id=2JNrT43eji
[call_log_7]
dir=0
status=0
from=sip:pauline@sip.linphone.org
to=sip:laure@sip.linphone.org
start_date_time=1441738200
duration=5
quality=4.715808
video_enabled=1
call_id=joyRwGhk-2
[call_log_8]
dir=1
status=3
from=sip:laure@sip.linphone.org
to=sip:pauline@sip.linphone.org
start_date_time=1441738122
duration=0
quality=-1.000000
video_enabled=0
call_id=az01erTY2U
[call_log_9]
dir=0
status=3
from=sip:pauline@sip.linphone.org
to=sip:laure@sip.linphone.org
start_date_time=1441738031
duration=0
quality=-1.000000
video_enabled=0
call_id=jk39dfZP0L