#include "RtcomEventLogger.h"
#include "EventProcessors/iEventProcessor.h"
+#include "EventTypes/eEventTypes.h"
#include "EventTypes/iEvent.h"
+#include "EventTypes/PhoneCall.h"
#include "EventTypes/SMS.h"
+#include "RtcomEventLoggerComponents/TriggerDisabler.h"
#include "Settings.h"
#include <QDebug>
+#include <QMutex>
+#include <QWaitCondition>
+
+// For reindexing
+#include <QDir>
+#include <QPair>
+#include <QStringList>
+#include <QtSql/QSqlDatabase>
+#include <QtSql/QSqlQuery>
+#include <QVariant>
#include <uuid/uuid.h>
#include <rtcom-eventlogger/event.h>
#include <rtcom-eventlogger/eventlogger.h>
+#include <stdexcept>
+
using namespace DBBackends;
+using namespace EventTypes;
QDebug operator<<(QDebug, RTComElEvent &);
QDebug operator<<(QDebug, RTComElAttachment &);
if(NULL != el)
{
// Grab the service IDs we want to work with
- m_ServiceIDs.insert(SERVICE_ID_CALL, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CALL"));
- m_ServiceIDs.insert(SERVICE_ID_CHAT, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CHAT"));
- m_ServiceIDs.insert(SERVICE_ID_SMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"));
- m_ServiceIDs.insert(SERVICE_ID_MMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_MMS"));
+ m_ServiceIDs.insert(EVENT_TYPE_CALL, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CALL"));
+ //m_ServiceIDs.insert(EVENT_TYPE_CHAT, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CHAT"));
+ m_ServiceIDs.insert(EVENT_TYPE_SMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"));
+ //m_ServiceIDs.insert(EVENT_TYPE_MMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_MMS"));
// Remove any service IDs that weren't found
- foreach(ServiceID serviceID, m_ServiceIDs.keys())
- if(m_ServiceIDs.value(serviceID) == -1)
- m_ServiceIDs.remove(serviceID);
+ foreach(EventTypes::eEventTypes service, m_ServiceIDs.keys())
+ if(m_ServiceIDs.value(service) == -1)
+ m_ServiceIDs.remove(service);
g_object_unref(el);
}
RTComEl *el = rtcom_el_new();
if(NULL != el)
{
+ foreach(eEventTypes service, m_ServiceIDs.keys())
+ ProcessService(processor, service, *el);
+
+ g_object_unref(el);
+ }
+ else
+ qDebug() << "Failed to create event logger.";
+}
+
+void RtcomEventLogger::ProcessService(EventProcessors::iEventProcessor &processor, const EventTypes::eEventTypes service, const RTComEl &el)
+{
+ RTComEl *el_nonconst(const_cast<RTComEl *>(&el));
+
+ bool incoming = CurrentSettings().ShouldProcess( Settings::INCOMING, service);
+ bool outgoing = CurrentSettings().ShouldProcess( Settings::OUTGOING, service);
+
+ if(incoming || outgoing)
+ {
// Initialise a query
- RTComElQuery *query = rtcom_el_query_new(el);
+ RTComElQuery *query = rtcom_el_query_new(el_nonconst);
if(query != NULL)
{
- bool incoming = CurrentSettings().ShouldProcess( Settings::TYPE_RECIEVED, Settings::EVENTTYPE_SMS);
- bool outgoing = CurrentSettings().ShouldProcess( Settings::TYPE_SENT, Settings::EVENTTYPE_SMS);
-
// Prepare it...
bool prepared = false;
if(incoming && outgoing)
{
prepared = rtcom_el_query_prepare(query,
"service-id",
- m_ServiceIDs.value(SERVICE_ID_SMS),
+ m_ServiceIDs.value(service),
RTCOM_EL_OP_EQUAL,
NULL);
{
prepared = rtcom_el_query_prepare(query,
"service-id",
- m_ServiceIDs.value(SERVICE_ID_SMS),
+ m_ServiceIDs.value(service),
RTCOM_EL_OP_EQUAL,
"outgoing",
if(prepared)
{
- RTComElIter *it = rtcom_el_get_events(el, query);
+ RTComElIter *it = rtcom_el_get_events(el_nonconst, query);
if(it != NULL)
{
if(rtcom_el_iter_first(it))
// Reset the iterator and grab the actual values
qDebug() << "Resetting iterator...";
g_object_unref(it);
- it = rtcom_el_get_events(el, query);
+ it = rtcom_el_get_events(el_nonconst, query);
if(it != NULL)
{
if(rtcom_el_iter_first(it))
RTComElEvent revent;
memset(&revent, 0, sizeof(revent));
- if(rtcom_el_iter_get_values (
- it,
- "id", &revent.fld_id,
- "service-id", &revent.fld_service_id,
- "start-time", &revent.fld_start_time,
- "end-time", &revent.fld_end_time,
- "local-uid", &revent.fld_local_uid,
- "local-name", &revent.fld_local_name,
- "remote-uid", &revent.fld_remote_uid,
- "remote-name", &revent.fld_remote_name,
- "is-read", &revent.fld_is_read,
- "outgoing", &revent.fld_outgoing,
- "free-text", &revent.fld_free_text,
- NULL))
+ if(rtcom_el_iter_get_full(it, &revent))
{
qDebug() << revent;
QList<RTComElAttachment *> rattachments;
RTComElAttachIter *at_it = rtcom_el_iter_get_attachments(it);
- if(it != NULL)
+ if(at_it != NULL)
{
qDebug() << "Attachments OK";
if(rtcom_el_attach_iter_first(at_it))
}
else
qDebug() << "Failed to create query.";
-
- g_object_unref(el);
}
else
- qDebug() << "Failed to create event logger.";
+ qDebug() << "Nothing to do for " << m_ServiceIDs.value(service);
}
EventTypes::iEvent *const RtcomEventLogger::CreateEvent(RTComElEvent &revent, QList<RTComElAttachment*> &rattachments)
{
- //if(m_ServiceIDs.contains(SERVICE_ID_CALL) && revent.fld_service_id == m_ServiceIDs.value(SERVICE_ID_CALL))
- // return new EventTypes::Call(CurrentSettings(), revent, rattachments);
+ if(m_ServiceIDs.contains(EVENT_TYPE_CALL) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CALL))
+ return new EventTypes::PhoneCall(CurrentSettings(), revent, rattachments);
- //if(m_ServiceIDs.contains(SERVICE_ID_CHAT) && revent.fld_service_id == m_ServiceIDs.value(SERVICE_ID_CHAT))
+ //if(m_ServiceIDs.contains(EVENT_TYPE_CHAT) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CHAT))
// return new EventTypes::Chat(CurrentSettings(), revent, rattachments);
- if(m_ServiceIDs.contains(SERVICE_ID_SMS) && revent.fld_service_id == m_ServiceIDs.value(SERVICE_ID_SMS))
+ if(m_ServiceIDs.contains(EVENT_TYPE_SMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_SMS))
return new EventTypes::SMS(CurrentSettings(), revent, rattachments);
- //if(m_ServiceIDs.contains(SERVICE_ID_MMS) && revent.fld_service_id == m_ServiceIDs.value(SERVICE_ID_MMS))
+ //if(m_ServiceIDs.contains(EVENT_TYPE_MMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_MMS))
// return new EventTypes::MMS(CurrentSettings(), revent, rattachments);
return NULL;
}
+void RtcomEventLogger::PreInsert()
+{
+ m_TriggerDisabler = new RtcomEventLoggerComponents::TriggerDisabler(CurrentSettings());
+}
+
void RtcomEventLogger::Insert(EventTypes::iEvent &event, const NumberToNameLookup &numberToNameLookup)
{
if(EventTypes::RtcomEvent *rtcomEvent = dynamic_cast<EventTypes::RtcomEvent *>(&event))
{
const uint UUID_STR_LEN(36);
- int newEventID(0);
_RTComEl *el(rtcom_el_new());
if(NULL != el)
{
qDebug() << *rattachments;
// Add the event
- newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error);
- qDebug() << "new id: " << newEventID;
- if(error != NULL)
+ QDateTime startTime(QDateTime::currentDateTimeUtc());
+ int newEventID(-1);
+ int currentBackoffInMillisecs(5);
+ while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
+ && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 10000)
+ {
+ if(error != NULL)
+ {
+ qDebug() << "err: " << error->message;
+ g_error_free(error);
+ error = NULL;
+ }
+
+ // Don't hammer the DB when there's an error. Give it literally just a moment before retrying.
+ QMutex mutex;
+ mutex.lock();
+
+ QWaitCondition waitCondition;
+ waitCondition.wait(&mutex, currentBackoffInMillisecs);
+
+ mutex.unlock();
+
+ // Exponential backoff...
+ currentBackoffInMillisecs *= 2;
+ }
+
+ if(-1 == newEventID)
+ {
+ qDebug() << "Unable to insert event due to error.";
+ qDebug() << *revent << "\n";
+ }
+ else
{
- qDebug() << "err: " << error->message;
- g_error_free(error);
+ qDebug() << "new id: " << newEventID;
+ InsertedIDs().append(newEventID);
}
// Release the attachments
return;
}
+void RtcomEventLogger::PostInsert()
+{
+ // Reorder the DB IDs as Nokia are guilty of both premature
+ // optimisation as well as closed source UIs...
+ Reindex();
+
+ delete m_TriggerDisabler;
+}
+
+void RtcomEventLogger::ClearInsertedIDs()
+{
+ InsertedIDs().clear();
+}
+
+// Reorder the DB IDs as Nokia are guilty of both premature
+// optimisation as well as closed source UIs...
+void RtcomEventLogger::Reindex()
+{
+ // Set up the database connection...
+ QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
+
+ db.setDatabaseName(CurrentSettings().DBPath());
+ if(!db.open())
+ {
+ throw std::runtime_error("Cannot open database: Unable to establish database connection");
+ }
+ else
+ {
+ // Reorder the evnts by their start time
+ uint changesRequired(0);
+ do
+ {
+ // Note the smallest event ID found, so we have a place to start.
+ int min(0);
+
+ // The required ID changes ( current, correct );
+ QHash<int, int> mapping;
+
+ // Grab the current records, and determine what changes need to
+ // happen to get to the sorted results
+ {
+ qDebug() << "DB Opened";
+
+ QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
+
+ dbq1->setForwardOnly( true );
+ dbq2->setForwardOnly( true );
+
+ QString s1("SELECT id, event_type_id, start_time, end_time "
+ " FROM Events");
+ QString s2("SELECT id, event_type_id, start_time, end_time "
+ " FROM Events ORDER BY start_time ASC");
+
+ if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
+ {
+ qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
+
+ while( dbq1->next() && dbq2->next())
+ {
+ int one (dbq1->value( 0 ).value< int >());
+ int two (dbq2->value( 0 ).value< int >());
+ //uint startTime( m_dbq->value( 1 ).value< uint >() );
+ //uint endTime( m_dbq->value( 2 ).value< uint >() );
+
+ //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
+ //qDebug() << "( " << one << ", " << two << " )";
+
+ if(two != one)
+ {
+ if(min == 0)
+ min = one;
+
+ //qDebug() << "( " << one << ", " << two << " )";
+ mapping.insert(one, two);
+ }
+ }
+ }
+ else
+ {
+ qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
+ qDebug() << "Query1: " << s1;
+ qDebug() << "Query2: " << s1;
+ }
+
+ // Clear up database connections
+ if ( dbq1 != NULL )
+ {
+ qDebug() << "Cleaning up connection 1";
+
+ dbq1->finish();
+
+ delete dbq1;
+ dbq1 = NULL;
+ }
+
+ if ( dbq2 != NULL )
+ {
+ qDebug() << "Cleaning up connection 2";
+
+ dbq2->finish();
+
+ delete dbq2;
+ dbq2 = NULL;
+ }
+ }
+
+ QList<int> sequence;
+ int val(min);
+ sequence.append(0);
+ sequence.append(val);
+ //qDebug().nospace() << "val1: " << val << ", ";
+
+ while((val = mapping[val]) && val != min)
+ {
+ sequence.append(val);
+ //qDebug().nospace() << val << ", ";
+ }
+ sequence.append(0);
+
+ //qDebug().nospace() << "seq: ";
+ QList<QPair<int,int> > updates;
+ int last(sequence.first());
+ foreach(int seq, sequence)
+ {
+ if(seq != last)
+ {
+ //qDebug().nospace() << seq << ", " << last << ", ";
+ updates.append(QPair<int,int>(seq, last));
+ }
+
+ last = seq;
+ }
+
+ // Used to keep iterating until no changes are required.
+ // TODO: Shouldn't be required, but is. One to revisit later.
+ changesRequired = updates.count();
+
+ for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
+ {
+ //qDebug().nospace() << (*it).first << ", " << (*it).second;
+ }
+
+ QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
+ QString query;
+ for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
+ {
+ QString curquery = "UPDATE %3 set %4 = %1 WHERE %4 = %2;";
+ for( QList<QPair<int,int> >::const_iterator currentUpdate(updates.constBegin()); currentUpdate != updates.constEnd(); ++currentUpdate)
+ {
+ query.append(
+ curquery
+ .arg((*currentUpdate).second)
+ .arg((*currentUpdate).first)
+ .arg((*currentTable))
+ .arg((*currentTable) == "Events" ? "id" : "event_id")
+ ).append("\n");
+
+ //qDebug().nospace() << (*it).first << ", " << (*it).second;
+ }
+ }
+
+ //qDebug() << query;
+
+ QSqlQuery * UpdateQuery(new QSqlQuery( db ));
+ if(UpdateQuery != NULL)
+ {
+ UpdateQuery->setForwardOnly( true );
+
+ if(db.transaction())
+ {
+ QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
+ try
+ {
+ for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
+ {
+ if (!UpdateQuery->exec(*currentStatement))
+ {
+ qDebug() << "Query Failed: " << *currentStatement;
+ throw std::exception();
+ }
+ }
+
+ qDebug() << "Committing.";
+ db.commit();
+ }
+ catch(...)
+ {
+ qDebug() << "Rolling back.";
+ db.rollback();
+ }
+ }
+ else
+ qDebug() << "Unable to start transaction.";
+ }
+ }while(changesRequired > 0);
+
+ qDebug() << "Closing.";
+ db.close();
+ QSqlDatabase::removeDatabase( "QSQLITE" );
+ }
+
+ return;
+}
+
QDebug operator<<(QDebug dbg, RTComElEvent &event)
{
dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
- dbg.nospace() << "\tFolder:\t\t" << (event.fld_outgoing ? "Sent" : "Inbox") << "\n";
- dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time) << "\n";
- dbg.nospace() << "\tend-time:\t\t" << QDateTime::fromTime_t(event.fld_end_time) << "\n";
- //dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
- //dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
+ dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
+ dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
+ dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
+ dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
+ dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time).toUTC() << "\n";
+ dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time).toUTC() << "\n";
+ dbg.nospace() << "\tend-time:\t" << QDateTime::fromTime_t(event.fld_end_time).toUTC() << "\n";
+ dbg.nospace() << "\tis-read:\t" << (event.fld_is_read ? "true" : "false") << "\n";
+ dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
+ dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
+ dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
+ dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
+ dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
+ dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
- dbg.nospace() << "\tis-read:\t\t" << (event.fld_is_read ? "true" : "false") << "\n";
+ dbg.nospace() << "\tremote-ebid:\t" << event.fld_remote_ebook_uid << "\n";
+ dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";