/*
Copyright (C) 2005-2007 Michel de Boer <michel@twinklephone.com>
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 <cassert>
#include <cstdlib>
#include <sys/stat.h>
#include <iostream>
#include <fstream>
#include <vector>
#include "call_history.h"
#include "log.h"
#include "sys_settings.h"
#include "translator.h"
#include "userintf.h"
#include "util.h"
// Call history file
#define CALL_HISTORY_FILE "twinkle.ch";
// Field seperator in call history file
#define REC_SEPARATOR '|'
////////////////////////
// class t_call_record
////////////////////////
t_mutex t_call_record::mtx_class;
unsigned short t_call_record::next_id = 1;
t_call_record::t_call_record() {
mtx_class.lock();
id = next_id++;
if (next_id == 65535) next_id = 1;
mtx_class.unlock();
time_start = 0;
time_answer = 0;
time_end = 0;
invite_resp_code = 0;
}
void t_call_record::renew() {
mtx_class.lock();
id = next_id++;
if (next_id == 65535) next_id = 1;
mtx_class.unlock();
time_start = 0;
time_answer = 0;
time_end = 0;
direction = DIR_IN;
from_display.clear();
from_uri.set_url("");
from_organization.clear();
to_display.clear();
to_uri.set_url("");
to_organization.clear();
reply_to_display.clear();
reply_to_uri.set_url("");
referred_by_display.clear();
referred_by_uri.set_url("");
subject.clear();
rel_cause = CS_LOCAL_USER;
invite_resp_code = 0;
invite_resp_reason.clear();
far_end_device.clear();
user_profile.clear();
}
void t_call_record::start_call(const t_request *invite, t_direction dir,
const string &_user_profile)
{
assert(invite->method == INVITE);
struct timeval t;
gettimeofday(&t, NULL);
time_start = t.tv_sec;
from_display = invite->hdr_from.get_display_presentation();
from_uri = invite->hdr_from.uri;
if (invite->hdr_organization.is_populated()) {
from_organization = invite->hdr_organization.name;
}
to_display = invite->hdr_to.display;
to_uri = invite->hdr_to.uri;
if (invite->hdr_reply_to.is_populated()) {
reply_to_display = invite->hdr_reply_to.display;
reply_to_uri = invite->hdr_reply_to.uri;
}
if (invite->hdr_referred_by.is_populated()) {
referred_by_display = invite->hdr_referred_by.display;
referred_by_uri = invite->hdr_referred_by.uri;
}
if (invite->hdr_subject.is_populated()) {
subject = invite->hdr_subject.subject;
}
direction = dir;
user_profile = _user_profile;
if (direction == DIR_IN && invite->hdr_user_agent.is_populated()) {
far_end_device = invite->hdr_user_agent.get_ua_info();
}
}
void t_call_record::fail_call(const t_response *resp) {
assert(resp->get_class() >= 3);
assert(resp->hdr_cseq.method == INVITE);
struct timeval t;
gettimeofday(&t, NULL);
time_end = t.tv_sec;
rel_cause = CS_FAILURE;
invite_resp_code = resp->code;
invite_resp_reason = resp->reason;
if (resp->hdr_organization.is_populated()) {
to_organization = resp->hdr_organization.name;
}
if (direction == DIR_OUT && resp->hdr_server.is_populated()) {
far_end_device = resp->hdr_server.get_server_info();
}
}
void t_call_record::answer_call(const t_response *resp) {
assert(resp->is_success());
struct timeval t;
gettimeofday(&t, NULL);
time_answer = t.tv_sec;
invite_resp_code = resp->code;
invite_resp_reason = resp->reason;
if (resp->hdr_organization.is_populated()) {
to_organization = resp->hdr_organization.name;
}
if (direction == DIR_OUT && resp->hdr_server.is_populated()) {
far_end_device = resp->hdr_server.get_server_info();
}
}
void t_call_record::end_call(t_rel_cause cause) {
struct timeval t;
gettimeofday(&t, NULL);
time_end = t.tv_sec;
rel_cause = cause;
}
void t_call_record::end_call(bool far_end) {
if (far_end) {
end_call(CS_REMOTE_USER);
} else {
end_call(CS_LOCAL_USER);
}
}
string t_call_record::get_rel_cause(void) const {
switch (rel_cause) {
case CS_LOCAL_USER:
return TRANSLATE2("CoreCallHistory", "local user");
case CS_REMOTE_USER:
return TRANSLATE2("CoreCallHistory", "remote user");
case CS_FAILURE:
return TRANSLATE2("CoreCallHistory", "failure");
}
return TRANSLATE2("CoreCallHistory", "unknown");
}
string t_call_record::get_rel_cause_internal(void) const {
switch (rel_cause) {
case CS_LOCAL_USER:
return "local user";
case CS_REMOTE_USER:
return "remote user";
case CS_FAILURE:
return "failure";
}
return "unknown";
}
string t_call_record::get_direction(void) const {
switch (direction) {
case DIR_IN:
return TRANSLATE2("CoreCallHistory", "in");
case DIR_OUT:
return TRANSLATE2("CoreCallHistory", "out");
}
return TRANSLATE2("CoreCallHistory", "unknown");
}
string t_call_record::get_direction_internal(void) const {
switch (direction) {
case DIR_IN:
return "in";
case DIR_OUT:
return "out";
}
return "unknown";
}
bool t_call_record::set_rel_cause(const string &cause) {
// NOTE: caller and callee were used before version 0.7
// They are still checked here for backward compatibility
if (cause == "caller" || cause == "local user") {
rel_cause = CS_LOCAL_USER;
} else if (cause == "callee" || cause == "remote user") {
rel_cause = CS_REMOTE_USER;
} else if (cause == "failure") {
rel_cause = CS_FAILURE;
} else {
return false;
}
return true;
}
bool t_call_record::set_direction(const string &dir) {
if (dir == "in") {
direction = DIR_IN;
} else if (dir == "out") {
direction = DIR_OUT;
} else {
return false;
}
return true;
}
bool t_call_record::create_file_record(vector<string> &v) const {
v.clear();
v.push_back(ulong2str(time_start));
v.push_back(ulong2str(time_answer));
v.push_back(ulong2str(time_end));
v.push_back(get_direction_internal());
v.push_back(from_display);
v.push_back(from_uri.encode());
v.push_back(from_organization);
v.push_back(to_display);
v.push_back(to_uri.encode());
v.push_back(to_organization);
v.push_back(reply_to_display);
v.push_back(reply_to_uri.encode());
v.push_back(referred_by_display);
v.push_back(referred_by_uri.encode());
v.push_back(subject);
v.push_back(get_rel_cause_internal());
v.push_back(int2str(invite_resp_code));
v.push_back(invite_resp_reason);
v.push_back(far_end_device);
v.push_back(user_profile);
return true;
}
bool t_call_record::populate_from_file_record(const vector<string> &v) {
// Check number of fields
if (v.size() != 20) return false;
time_start = strtoul(v[0].c_str(), NULL, 10);
time_answer = strtoul(v[1].c_str(), NULL, 10);
time_end = strtoul(v[2].c_str(), NULL, 10);
if (!set_direction(v[3])) return false;
from_display = v[4];
from_uri.set_url(v[5]);
if (!from_uri.is_valid()) return false;
from_organization = v[6];
to_display = v[7];
to_uri.set_url(v[8]);
if (!to_uri.is_valid()) return false;
to_organization = v[9];
reply_to_display = v[10];
reply_to_uri.set_url(v[11]);
referred_by_display = v[12];
referred_by_uri.set_url(v[13]);
subject = v[14];
if (!set_rel_cause(v[15])) return false;
invite_resp_code = atoi(v[16].c_str());
invite_resp_reason = v[17];
far_end_device = v[18];
user_profile = v[19];
return true;
}
bool t_call_record::is_valid(void) const {
if (time_start == 0 || time_end == 0) return false;
if (time_answer > 0 && rel_cause == CS_FAILURE) return false;
return true;
}
unsigned short t_call_record::get_id(void) const {
return id;
}
////////////////////////
// class t_call_history
////////////////////////
t_call_history::t_call_history() : utils::t_record_file<t_call_record>() {
set_header("time_start|time_answer|time_end|direction|from_display|from_uri|"
"from_organization|to_display|to_uri|to_organization|"
"reply_to_display|reply_to_uri|referred_by_display|referred_by_uri|"
"subject|rel_cause|invite_resp_code|invite_resp_reason|"
"far_end_device|user_profile");
set_separator(REC_SEPARATOR);
string s(DIR_HOME);
s += "/";
s += USER_DIR;
s += "/";
s += CALL_HISTORY_FILE;
set_filename(s);
num_missed_calls = 0;
}
void t_call_history::add_call_record(const t_call_record &call_record, bool write) {
if (!call_record.is_valid()) {
log_file->write_report("Call history record is not valid.",
"t_call_history::add_call_record", LOG_NORMAL, LOG_WARNING);
return;
}
mtx_records.lock();
records.push_back(call_record);
while (records.size() > sys_config->get_ch_max_size()) {
records.pop_front();
}
// Increment missed calls counter
if (call_record.rel_cause == t_call_record::CS_FAILURE &&
call_record.direction == t_call_record::DIR_IN)
{
++num_missed_calls;
ui->cb_missed_call(num_missed_calls);
}
mtx_records.unlock();
if (write) {
string msg;
if (!save(msg)) {
log_file->write_report(msg, "t_call_history::add_call_record",
LOG_NORMAL, LOG_WARNING);
}
}
// Update call history in user interface.
ui->cb_call_history_updated();
}
void t_call_history::delete_call_record(unsigned short id, bool write) {
mtx_records.lock();
for (list<t_call_record>::iterator i = records.begin();
i != records.end(); i++)
{
if (i->get_id() == id) {
records.erase(i);
break;
}
}
mtx_records.unlock();
if (write) {
string msg;
if (!save(msg)) {
log_file->write_report(msg, "t_call_history::delete_call_record",
LOG_NORMAL, LOG_WARNING);
}
}
// Update call history in user interface.
ui->cb_call_history_updated();
}
/*
bool t_call_history::read_history(string &error_msg) {
struct stat stat_buf;
mtx_records.lock();
records.clear();
// Check if call history file exists
if (stat(filename.c_str(), &stat_buf) != 0) {
// There is no call history file.
mtx_records.unlock();
return true;
}
// Open call history file
ifstream ch(filename.c_str());
if (!ch) {
error_msg = TRANSLATE("Cannot open file for reading: %1");
error_msg = replace_first(error_msg, "%1", filename);
mtx_records.unlock();
return false;
}
// Read and parse history file.
while (!ch.eof()) {
string line;
t_call_record rec;
getline(ch, line);
// Check if read operation succeeded
if (!ch.good() && !ch.eof()) {
error_msg = TRANSLATE("File system error while reading file %1 .");
error_msg = replace_first(error_msg, "%1", filename);
mtx_records.unlock();
return false;
}
line = trim(line);
// Skip empty lines
if (line.size() == 0) continue;
// Skip comment lines
if (line[0] == '#') continue;
// Add record. Skip records that cannot be parsed.
if (rec.populate_from_file_record(line)) {
add_call_record(rec, false);
}
}
mtx_records.unlock();
// Clear the number of missed calls as reading the history
// will have increased the number of missed calls for each
// record read.
clear_num_missed_calls();
return true;
}
bool t_call_history::write_history(string &error_msg) const {
struct stat stat_buf;
t_call_history *self = const_cast<t_call_history *>(this);
self->mtx_records.lock();
// Open file
ofstream ch(filename.c_str());
if (!ch) {
error_msg = TRANSLATE("Cannot open file for writing: %1");
error_msg = replace_first(error_msg, "%1", filename);
self->mtx_records.unlock();
return false;
}
// Write file header
ch << "# time_start|time_answer|time_end|direction|from_display|from_uri|"
"from_organization|to_display|to_uri|to_organization|"
"reply_to_display|reply_to_uri|referred_by_display|referred_by_uri|"
"subject|rel_cause|invite_resp_code|invite_resp_reason|"
"far_end_device|user_profile";
ch << endl;
// Write records
for (list<t_call_record>::const_iterator i = records.begin();
i != records.end(); i++)
{
ch << i->create_file_record();
ch << endl;
}
self->mtx_records.unlock();
if (!ch.good()) {
error_msg = TRANSLATE("File system error while writing file %1 .");
error_msg = replace_first(error_msg, "%1", filename);
return false;
}
return true;
}
*/
void t_call_history::get_history(list<t_call_record> &history) {
mtx_records.lock();
history = records;
mtx_records.unlock();
}
void t_call_history::clear(bool write) {
mtx_records.lock();
records.clear();
mtx_records.unlock();
if (write) {
string msg;
if (!save(msg)) {
log_file->write_report(msg, "t_call_history::clear",
LOG_NORMAL, LOG_WARNING);
}
}
// Update call history in user interface.
ui->cb_call_history_updated();
clear_num_missed_calls();
}
int t_call_history::get_num_missed_calls(void) const {
return num_missed_calls;
}
void t_call_history::clear_num_missed_calls(void) {
mtx_records.lock();
num_missed_calls = 0;
mtx_records.unlock();
ui->cb_missed_call(0);
}
syntax highlighted by Code2HTML, v. 0.9.1