2 * Copyright (C) 2011, Jamie Thompson
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public
15 * License along with this program; If not, see
16 * <http://www.gnu.org/licenses/>.
19 #include "RtcomEventLogger.h"
21 #include "EventProcessors/iEventProcessor.h"
22 #include "EventTypes/eEventTypes.h"
23 #include "EventTypes/iEvent.h"
24 #include "EventTypes/PhoneCall.h"
25 #include "EventTypes/SMS.h"
26 #include "RtcomEventLoggerComponents/TriggerDisabler.h"
31 #include <QWaitCondition>
36 #include <QStringList>
37 #include <QtSql/QSqlDatabase>
38 #include <QtSql/QSqlQuery>
41 #include <uuid/uuid.h>
43 #include <rtcom-eventlogger/event.h>
44 #include <rtcom-eventlogger/eventlogger.h>
48 using namespace DBBackends;
49 using namespace EventTypes;
51 QDebug operator<<(QDebug, RTComElEvent &);
52 QDebug operator<<(QDebug, RTComElAttachment &);
53 QDebug operator<<(QDebug, GList &);
54 QDebug operator<<(QDebug, QList<RTComElAttachment*> &);
56 RtcomEventLogger::RtcomEventLogger(const Settings &settings) :
59 RTComEl *el(rtcom_el_new());
62 // Grab the service IDs we want to work with
63 m_ServiceIDs.insert(EVENT_TYPE_CALL, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CALL"));
64 //m_ServiceIDs.insert(EVENT_TYPE_CHAT, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CHAT"));
65 m_ServiceIDs.insert(EVENT_TYPE_SMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"));
66 //m_ServiceIDs.insert(EVENT_TYPE_MMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_MMS"));
68 // Remove any service IDs that weren't found
69 foreach(EventTypes::eEventTypes service, m_ServiceIDs.keys())
70 if(m_ServiceIDs.value(service) == -1)
71 m_ServiceIDs.remove(service);
76 qDebug() << "Failed to create event logger.";
79 RtcomEventLogger::RtcomEventLogger(const Settings &settings, const EventTypes::RtcomEvent &event) :
84 void RtcomEventLogger::Process(EventProcessors::iEventProcessor &processor)
86 // Initialise the event logger
87 RTComEl *el = rtcom_el_new();
90 foreach(eEventTypes service, m_ServiceIDs.keys())
91 ProcessService(processor, service, *el);
96 qDebug() << "Failed to create event logger.";
99 void RtcomEventLogger::ProcessService(EventProcessors::iEventProcessor &processor, const EventTypes::eEventTypes service, const RTComEl &el)
101 RTComEl *el_nonconst(const_cast<RTComEl *>(&el));
103 bool incoming = CurrentSettings().ShouldProcess( Settings::INCOMING, service);
104 bool outgoing = CurrentSettings().ShouldProcess( Settings::OUTGOING, service);
106 if(incoming || outgoing)
108 // Initialise a query
109 RTComElQuery *query = rtcom_el_query_new(el_nonconst);
113 bool prepared = false;
114 if(incoming && outgoing)
116 prepared = rtcom_el_query_prepare(query,
118 m_ServiceIDs.value(service),
125 prepared = rtcom_el_query_prepare(query,
127 m_ServiceIDs.value(service),
137 qDebug() << "SQL:\n" << rtcom_el_query_get_sql(query);
141 RTComElIter *it = rtcom_el_get_events(el_nonconst, query);
144 if(rtcom_el_iter_first(it))
147 qDebug() << "Getting event count...";
148 while(rtcom_el_iter_next(it))
151 // Reset the iterator and grab the actual values
152 qDebug() << "Resetting iterator...";
154 it = rtcom_el_get_events(el_nonconst, query);
157 if(rtcom_el_iter_first(it))
160 qDebug() << "Getting events...";
164 qDebug() << "Event #" << idx;
167 memset(&revent, 0, sizeof(revent));
169 if(rtcom_el_iter_get_full(it, &revent))
173 QList<RTComElAttachment *> rattachments;
174 RTComElAttachIter *at_it = rtcom_el_iter_get_attachments(it);
177 qDebug() << "Attachments OK";
178 if(rtcom_el_attach_iter_first(at_it))
180 qDebug() << "Getting events...";
184 rattachments.append(rtcom_el_attach_iter_get(at_it));
185 qDebug() << "Attachment ID #" << rattachments.last()->id << endl;
186 qDebug() << "desc: " << rattachments.last()->desc << endl;
187 qDebug() << "path: " << rattachments.last()->path << endl;
188 }while(rtcom_el_attach_iter_next(at_it));
192 EventTypes::iEvent *const newEvent(CreateEvent(revent, rattachments));
193 processor.Process(*newEvent);
196 processor.EmitEventProcessed(idx, eventCount);
199 rtcom_el_event_free_contents(&revent);
201 while(rtcom_el_iter_next(it));
202 qDebug() << "...all events retrieved.";
206 qDebug() << "Failed to reset iterator";
209 qDebug() << "Failed to start iterator";
212 qDebug() << "Failed to get iterator. Do you have any events?";
215 qDebug() << "Failed to prepare the query.";
217 g_object_unref(query);
220 qDebug() << "Failed to create query.";
223 qDebug() << "Nothing to do for " << m_ServiceIDs.value(service);
226 EventTypes::iEvent *const RtcomEventLogger::CreateEvent(RTComElEvent &revent, QList<RTComElAttachment*> &rattachments)
228 if(m_ServiceIDs.contains(EVENT_TYPE_CALL) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CALL))
229 return new EventTypes::PhoneCall(CurrentSettings(), revent, rattachments);
231 //if(m_ServiceIDs.contains(EVENT_TYPE_CHAT) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CHAT))
232 // return new EventTypes::Chat(CurrentSettings(), revent, rattachments);
234 if(m_ServiceIDs.contains(EVENT_TYPE_SMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_SMS))
235 return new EventTypes::SMS(CurrentSettings(), revent, rattachments);
237 //if(m_ServiceIDs.contains(EVENT_TYPE_MMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_MMS))
238 // return new EventTypes::MMS(CurrentSettings(), revent, rattachments);
243 void RtcomEventLogger::PreInsert()
245 m_TriggerDisabler = new RtcomEventLoggerComponents::TriggerDisabler(CurrentSettings());
248 void RtcomEventLogger::Insert(EventTypes::iEvent &event, const NumberToNameLookup &numberToNameLookup)
250 if(EventTypes::RtcomEvent *rtcomEvent = dynamic_cast<EventTypes::RtcomEvent *>(&event))
252 const uint UUID_STR_LEN(36);
254 _RTComEl *el(rtcom_el_new());
257 // Convert our objects into RTCom structs
258 RTComElEvent *revent(rtcomEvent->toRTComEvent(numberToNameLookup));
259 GList *rattachments(event.Attachments().toRTComAttachments());
263 // Generate the headers for the event
264 GHashTable *rheaders(g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free));
266 char key[UUID_STR_LEN + 1];
267 uuid_generate_random(uuid);
268 uuid_unparse(uuid, key);
269 g_hash_table_insert(rheaders, g_strdup ("message-token"), key);
270 qDebug() << "headers: " << rheaders;
272 qDebug() << "Inserting event:";
274 qDebug() << *rattachments;
277 QDateTime startTime(QDateTime::currentDateTimeUtc());
279 int currentBackoffInMillisecs(5);
280 while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
281 && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 10000)
285 qDebug() << "err: " << error->message;
290 // Don't hammer the DB when there's an error. Give it literally just a moment before retrying.
294 QWaitCondition waitCondition;
295 waitCondition.wait(&mutex, currentBackoffInMillisecs);
299 // Exponential backoff...
300 currentBackoffInMillisecs *= 2;
305 qDebug() << "Unable to insert event due to error.";
306 qDebug() << *revent << "\n";
310 qDebug() << "new id: " << newEventID;
311 InsertedIDs().append(newEventID);
314 // Release the attachments
315 g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
316 g_list_free (rattachments);
318 rtcom_el_event_free_contents(revent);
319 rtcom_el_event_free(revent);
322 qDebug() << "Unable to initalise eventlogger for insertion.";
330 void RtcomEventLogger::PostInsert()
332 // Our new events get the specified storage times ignored, and some things
333 // use these, so bodge them for now.
334 UpdateInsertedStorageTimes();
336 // Reorder the DB IDs as Nokia are guilty of both premature
337 // optimisation as well as closed source UIs...
340 delete m_TriggerDisabler;
343 void RtcomEventLogger::ClearInsertedIDs()
345 InsertedIDs().clear();
348 void RtcomEventLogger::UpdateInsertedStorageTimes()
350 // Set up the database connection...
351 QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
353 db.setDatabaseName(CurrentSettings().DBPath());
356 // Update storage time as some software uses it...
357 QSqlQuery * updateStorageTimeQuery(new QSqlQuery(db));
358 if(updateStorageTimeQuery != NULL)
360 updateStorageTimeQuery->setForwardOnly( true );
366 QString sqlUpdateStorageTime(QString("UPDATE events SET storage_time = start_time WHERE id IN (%1)")
367 .arg(IntsToStringList(InsertedIDs()).join(",")));
368 if (!updateStorageTimeQuery->exec(sqlUpdateStorageTime))
370 qDebug() << "Query Failed: " << sqlUpdateStorageTime;
371 throw std::exception();
374 qDebug() << "Committing.";
379 qDebug() << "Rolling back.";
384 qDebug() << "Unable to start transaction.";
388 throw std::runtime_error("Cannot open database: Unable to establish database connection");
391 // Reorder the DB IDs as Nokia are guilty of both premature
392 // optimisation as well as closed source UIs...
393 // NOTE: The InsertedID list will be invalid after this so call it last...
394 void RtcomEventLogger::Reindex()
396 // Set up the database connection...
397 QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
399 db.setDatabaseName(CurrentSettings().DBPath());
402 // Reorder the evnts by their start time
403 uint changesRequired(0);
406 // Note the smallest event ID found, so we have a place to start.
409 // The required ID changes ( current, correct );
410 QHash<int, int> mapping;
412 // Grab the current records, and determine what changes need to
413 // happen to get to the sorted results
415 qDebug() << "DB Opened";
417 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
419 dbq1->setForwardOnly( true );
420 dbq2->setForwardOnly( true );
422 QString s1("SELECT id, event_type_id, start_time, end_time "
424 QString s2("SELECT id, event_type_id, start_time, end_time "
425 " FROM Events ORDER BY start_time ASC");
427 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
429 qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
431 while( dbq1->next() && dbq2->next())
433 int one (dbq1->value( 0 ).value< int >());
434 int two (dbq2->value( 0 ).value< int >());
435 //uint startTime( m_dbq->value( 1 ).value< uint >() );
436 //uint endTime( m_dbq->value( 2 ).value< uint >() );
438 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
439 //qDebug() << "( " << one << ", " << two << " )";
446 //qDebug() << "( " << one << ", " << two << " )";
447 mapping.insert(one, two);
453 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
454 qDebug() << "Query1: " << s1;
455 qDebug() << "Query2: " << s1;
458 // Clear up database connections
461 qDebug() << "Cleaning up connection 1";
471 qDebug() << "Cleaning up connection 2";
483 sequence.append(val);
484 //qDebug().nospace() << "val1: " << val << ", ";
486 while((val = mapping[val]) && val != min)
488 sequence.append(val);
489 //qDebug().nospace() << val << ", ";
493 //qDebug().nospace() << "seq: ";
494 QList<QPair<int,int> > updates;
495 int last(sequence.first());
496 foreach(int seq, sequence)
500 //qDebug().nospace() << seq << ", " << last << ", ";
501 updates.append(QPair<int,int>(seq, last));
507 // Used to keep iterating until no changes are required.
508 // TODO: Shouldn't be required, but is. One to revisit later.
509 changesRequired = updates.count();
511 for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
513 //qDebug().nospace() << (*it).first << ", " << (*it).second;
516 QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
518 for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
520 QString curquery = "UPDATE %3 set %4 = %1 WHERE %4 = %2;";
521 for( QList<QPair<int,int> >::const_iterator currentUpdate(updates.constBegin()); currentUpdate != updates.constEnd(); ++currentUpdate)
525 .arg((*currentUpdate).second)
526 .arg((*currentUpdate).first)
527 .arg((*currentTable))
528 .arg((*currentTable) == "Events" ? "id" : "event_id")
531 //qDebug().nospace() << (*it).first << ", " << (*it).second;
537 QSqlQuery * UpdateQuery(new QSqlQuery( db ));
538 if(UpdateQuery != NULL)
540 UpdateQuery->setForwardOnly( true );
544 QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
547 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
549 if (!UpdateQuery->exec(*currentStatement))
551 qDebug() << "Query Failed: " << *currentStatement;
552 throw std::exception();
556 qDebug() << "Committing.";
561 qDebug() << "Rolling back.";
566 qDebug() << "Unable to start transaction.";
568 }while(changesRequired > 0);
570 qDebug() << "Closing.";
572 QSqlDatabase::removeDatabase( "QSQLITE" );
575 throw std::runtime_error("Cannot open database: Unable to establish database connection");
580 QStringList RtcomEventLogger::IntsToStringList(QList<uint> &values)
582 QStringList returnValues;
583 returnValues.reserve(values.count());
585 foreach(uint value, values)
586 returnValues.append(QString::number(value));
591 QDebug operator<<(QDebug dbg, RTComElEvent &event)
593 dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
594 dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
595 dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
596 dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
597 dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
598 dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time).toUTC() << "\n";
599 dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time).toUTC() << "\n";
600 dbg.nospace() << "\tend-time:\t" << QDateTime::fromTime_t(event.fld_end_time).toUTC() << "\n";
601 dbg.nospace() << "\tis-read:\t" << (event.fld_is_read ? "true" : "false") << "\n";
602 dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
603 dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
604 dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
605 dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
606 dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
607 dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
608 dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
609 dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
610 dbg.nospace() << "\tremote-ebid:\t" << event.fld_remote_ebook_uid << "\n";
611 dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
612 dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
613 dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";
618 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
620 dbg.nospace() << "Event-id:\t" << attachment.event_id << "\n";
621 dbg.nospace() << "Path:\t" << attachment.path << "\n";
622 dbg.nospace() << "Desc:\t" << attachment.desc << "\n";
627 QDebug operator<<(QDebug dbg, GList &attachments)
629 dbg.nospace() << "Attachments" << "\n";
631 for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
633 qDebug() << *(RTComElAttachment*)attachment->data;
636 dbg.nospace() << "\n";
641 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
643 dbg.nospace() << "Attachments" << "\n";
645 foreach(RTComElAttachment *attachment, attachments)
646 dbg.nospace() << *attachment << "\n";
648 dbg.nospace() << "\n";