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 while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
280 && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 1000)
284 qDebug() << "err: " << error->message;
285 qDebug() << *revent << "\n";
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, 5);
302 qDebug() << "new id: " << newEventID;
303 InsertedIDs().append(newEventID);
306 qDebug() << "Unable to insert event due to error.";
308 // Release the attachments
309 g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
310 g_list_free (rattachments);
312 rtcom_el_event_free_contents(revent);
313 rtcom_el_event_free(revent);
316 qDebug() << "Unable to initalise eventlogger for insertion.";
324 void RtcomEventLogger::PostInsert()
326 // Reorder the DB IDs as Nokia are guilty of both premature
327 // optimisation as well as closed source UIs...
330 delete m_TriggerDisabler;
333 void RtcomEventLogger::ClearInsertedIDs()
335 InsertedIDs().clear();
338 // Reorder the DB IDs as Nokia are guilty of both premature
339 // optimisation as well as closed source UIs...
340 void RtcomEventLogger::Reindex()
342 // Set up the database connection...
343 QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
345 db.setDatabaseName(CurrentSettings().DBPath());
348 throw std::runtime_error("Cannot open database: Unable to establish database connection");
352 // Reorder the evnts by their start time
353 uint changesRequired(0);
356 // Note the smallest event ID found, so we have a place to start.
359 // The required ID changes ( current, correct );
360 QHash<int, int> mapping;
362 // Grab the current records, and determine what changes need to
363 // happen to get to the sorted results
365 qDebug() << "DB Opened";
367 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
369 dbq1->setForwardOnly( true );
370 dbq2->setForwardOnly( true );
372 QString s1("SELECT id, event_type_id, start_time, end_time "
374 QString s2("SELECT id, event_type_id, start_time, end_time "
375 " FROM Events ORDER BY start_time ASC");
377 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
379 qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
381 while( dbq1->next() && dbq2->next())
383 int one (dbq1->value( 0 ).value< int >());
384 int two (dbq2->value( 0 ).value< int >());
385 //uint startTime( m_dbq->value( 1 ).value< uint >() );
386 //uint endTime( m_dbq->value( 2 ).value< uint >() );
388 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
389 //qDebug() << "( " << one << ", " << two << " )";
396 qDebug() << "( " << one << ", " << two << " )";
397 mapping.insert(one, two);
403 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
404 qDebug() << "Query1: " << s1;
405 qDebug() << "Query2: " << s1;
408 // Clear up database connections
411 qDebug() << "Cleaning up connection 1";
421 qDebug() << "Cleaning up connection 2";
433 sequence.append(val);
434 qDebug().nospace() << "val1: " << val << ", ";
436 while((val = mapping[val]) && val != min)
438 sequence.append(val);
439 qDebug().nospace() << val << ", ";
443 qDebug().nospace() << "seq: ";
444 QList<QPair<int,int> > updates;
445 int last(sequence.first());
446 foreach(int seq, sequence)
450 qDebug().nospace() << seq << ", " << last << ", ";
451 updates.append(QPair<int,int>(seq, last));
457 // Used to keep iterating until no changes are required.
458 // TODO: Shouldn't be required, but is. One to revisit later.
459 changesRequired = updates.count();
461 for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
463 //qDebug().nospace() << (*it).first << ", " << (*it).second;
466 QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
468 for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
470 QString curquery = "UPDATE %3 set %4 = %1 WHERE %4 = %2;";
471 for( QList<QPair<int,int> >::const_iterator currentUpdate(updates.constBegin()); currentUpdate != updates.constEnd(); ++currentUpdate)
475 .arg((*currentUpdate).second)
476 .arg((*currentUpdate).first)
477 .arg((*currentTable))
478 .arg((*currentTable) == "Events" ? "id" : "event_id")
481 //qDebug().nospace() << (*it).first << ", " << (*it).second;
487 QSqlQuery * UpdateQuery(new QSqlQuery( db ));
488 if(UpdateQuery != NULL)
490 UpdateQuery->setForwardOnly( true );
494 QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
497 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
499 if ( UpdateQuery->exec( *currentStatement ))
500 qDebug() << "Query OK, " << UpdateQuery->numRowsAffected() << " rows affected.";
503 qDebug() << "Query Failed: " << *currentStatement;
504 throw std::exception();
508 qDebug() << "Committing.";
513 qDebug() << "Rolling back.";
518 qDebug() << "Unable to start transaction.";
520 }while(changesRequired > 0);
522 // Update the group cache so the last events are correct
524 qDebug() << "Updating most recent events.";
526 // Grab group UIDs from group cache
527 QSqlQuery * dbq(new QSqlQuery( db ));
528 dbq->setForwardOnly( true );
530 const char * groupUIDListSQL("SELECT group_uid FROM GroupCache");
531 if (dbq->exec(groupUIDListSQL))
533 qDebug() << "Query OK, " << dbq->numRowsAffected() << " rows affected.";
534 qDebug() << "GroupUIDs:";
536 QSet<QString> groupUIDs;
539 QString groupUID(dbq->value(0).value<QString>());
541 qDebug() << groupUID;
542 groupUIDs.insert(groupUID);
545 // Iterate over group UIDS
546 if(groupUIDs.count() > 0)
548 // Build a batch statement to update every group with
549 // the most recent event
551 // Ignore 'data' failures (i.e. no events but present in the
552 // cache)- something else's been monkeying with the DB, and
553 // we can't account for everything.
554 const QString updateGroupCacheWithLatestEventsSQL(
555 "UPDATE OR IGNORE GroupCache SET event_id = "
556 "(SELECT id FROM events WHERE group_uid = \"%1\" "
557 " ORDER BY id DESC LIMIT 1)"
558 " WHERE group_uid = \"%1\";");
559 QString updateGroupCacheWithLatestEventsBatchSQL;
560 foreach(QString groupUID, groupUIDs)
562 updateGroupCacheWithLatestEventsBatchSQL.append(
563 updateGroupCacheWithLatestEventsSQL
568 // Execute the statement in single-statement chunks thanks
569 // to QT's inability to call the SQLite function supporting
570 // multiple statements
572 QSqlQuery * setLatestEventInGroupCacheSQL(new QSqlQuery( db ));
573 if(NULL != setLatestEventInGroupCacheSQL)
575 setLatestEventInGroupCacheSQL->setForwardOnly( true );
579 QStringList statements = updateGroupCacheWithLatestEventsBatchSQL.trimmed().split(";", QString::SkipEmptyParts);
582 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
584 if ( setLatestEventInGroupCacheSQL->exec( *currentStatement ))
585 qDebug() << "Query OK, " << setLatestEventInGroupCacheSQL->numRowsAffected() << " rows affected.";
588 qDebug() << "Query Failed: " << *currentStatement;
589 throw std::exception();
593 qDebug() << "Committing.";
598 qDebug() << "Rolling back.";
603 qDebug() << "Unable to start transaction.";
609 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
610 qDebug() << "Query: " << groupUIDListSQL;
614 qDebug() << "Closing.";
616 QSqlDatabase::removeDatabase( "QSQLITE" );
622 QDebug operator<<(QDebug dbg, RTComElEvent &event)
624 dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
625 dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
626 dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
627 dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
628 dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
629 dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time) << "\n";
630 dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time) << "\n";
631 dbg.nospace() << "\tend-time:\t\t" << QDateTime::fromTime_t(event.fld_end_time) << "\n";
632 dbg.nospace() << "\tis-read:\t\t" << (event.fld_is_read ? "true" : "false") << "\n";
633 dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
634 dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
635 dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
636 dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
637 dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
638 dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
639 dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
640 dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
641 dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
642 dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
643 dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";
648 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
650 dbg.nospace() << "Event-id:\t" << attachment.event_id << "\n";
651 dbg.nospace() << "Path:\t" << attachment.path << "\n";
652 dbg.nospace() << "Desc:\t" << attachment.desc << "\n";
657 QDebug operator<<(QDebug dbg, GList &attachments)
659 dbg.nospace() << "Attachments" << "\n";
661 for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
663 qDebug() << *(RTComElAttachment*)attachment->data;
666 dbg.nospace() << "\n";
671 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
673 dbg.nospace() << "Attachments" << "\n";
675 foreach(RTComElAttachment *attachment, attachments)
676 dbg.nospace() << *attachment << "\n";
678 dbg.nospace() << "\n";