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 // Reorder the DB IDs as Nokia are guilty of both premature
333 // optimisation as well as closed source UIs...
336 delete m_TriggerDisabler;
339 void RtcomEventLogger::ClearInsertedIDs()
341 InsertedIDs().clear();
344 // Reorder the DB IDs as Nokia are guilty of both premature
345 // optimisation as well as closed source UIs...
346 void RtcomEventLogger::Reindex()
348 // Set up the database connection...
349 QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
351 db.setDatabaseName(CurrentSettings().DBPath());
354 throw std::runtime_error("Cannot open database: Unable to establish database connection");
358 // Reorder the evnts by their start time
359 uint changesRequired(0);
362 // Note the smallest event ID found, so we have a place to start.
365 // The required ID changes ( current, correct );
366 QHash<int, int> mapping;
368 // Grab the current records, and determine what changes need to
369 // happen to get to the sorted results
371 qDebug() << "DB Opened";
373 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
375 dbq1->setForwardOnly( true );
376 dbq2->setForwardOnly( true );
378 QString s1("SELECT id, event_type_id, start_time, end_time "
380 QString s2("SELECT id, event_type_id, start_time, end_time "
381 " FROM Events ORDER BY start_time ASC");
383 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
385 qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
387 while( dbq1->next() && dbq2->next())
389 int one (dbq1->value( 0 ).value< int >());
390 int two (dbq2->value( 0 ).value< int >());
391 //uint startTime( m_dbq->value( 1 ).value< uint >() );
392 //uint endTime( m_dbq->value( 2 ).value< uint >() );
394 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
395 //qDebug() << "( " << one << ", " << two << " )";
402 //qDebug() << "( " << one << ", " << two << " )";
403 mapping.insert(one, two);
409 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
410 qDebug() << "Query1: " << s1;
411 qDebug() << "Query2: " << s1;
414 // Clear up database connections
417 qDebug() << "Cleaning up connection 1";
427 qDebug() << "Cleaning up connection 2";
439 sequence.append(val);
440 //qDebug().nospace() << "val1: " << val << ", ";
442 while((val = mapping[val]) && val != min)
444 sequence.append(val);
445 //qDebug().nospace() << val << ", ";
449 //qDebug().nospace() << "seq: ";
450 QList<QPair<int,int> > updates;
451 int last(sequence.first());
452 foreach(int seq, sequence)
456 //qDebug().nospace() << seq << ", " << last << ", ";
457 updates.append(QPair<int,int>(seq, last));
463 // Used to keep iterating until no changes are required.
464 // TODO: Shouldn't be required, but is. One to revisit later.
465 changesRequired = updates.count();
467 for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
469 //qDebug().nospace() << (*it).first << ", " << (*it).second;
472 QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
474 for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
476 QString curquery = "UPDATE %3 set %4 = %1 WHERE %4 = %2;";
477 for( QList<QPair<int,int> >::const_iterator currentUpdate(updates.constBegin()); currentUpdate != updates.constEnd(); ++currentUpdate)
481 .arg((*currentUpdate).second)
482 .arg((*currentUpdate).first)
483 .arg((*currentTable))
484 .arg((*currentTable) == "Events" ? "id" : "event_id")
487 //qDebug().nospace() << (*it).first << ", " << (*it).second;
493 QSqlQuery * UpdateQuery(new QSqlQuery( db ));
494 if(UpdateQuery != NULL)
496 UpdateQuery->setForwardOnly( true );
500 QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
503 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
505 if (!UpdateQuery->exec(*currentStatement))
507 qDebug() << "Query Failed: " << *currentStatement;
508 throw std::exception();
512 qDebug() << "Committing.";
517 qDebug() << "Rolling back.";
522 qDebug() << "Unable to start transaction.";
524 }while(changesRequired > 0);
526 // Update the group cache so the last events are correct
528 qDebug() << "Updating most recent events.";
530 // Grab group UIDs from group cache
531 QSqlQuery * dbq(new QSqlQuery( db ));
532 dbq->setForwardOnly( true );
534 const char * groupUIDListSQL("SELECT group_uid FROM GroupCache");
535 if (dbq->exec(groupUIDListSQL))
537 qDebug() << "Query OK, " << dbq->numRowsAffected() << " rows affected.";
538 qDebug() << "GroupUIDs:";
540 QSet<QString> groupUIDs;
543 QString groupUID(dbq->value(0).value<QString>());
545 qDebug() << groupUID;
546 groupUIDs.insert(groupUID);
549 // Iterate over group UIDS
550 if(groupUIDs.count() > 0)
552 // Build a batch statement to update every group with
553 // the most recent event
555 // Ignore 'data' failures (i.e. no events but present in the
556 // cache)- something else's been monkeying with the DB, and
557 // we can't account for everything.
558 const QString updateGroupCacheWithLatestEventsSQL(
559 "UPDATE OR IGNORE GroupCache SET event_id = "
560 "(SELECT id FROM events WHERE group_uid = \"%1\" "
561 " ORDER BY id DESC LIMIT 1)"
562 " WHERE group_uid = \"%1\";");
563 QString updateGroupCacheWithLatestEventsBatchSQL;
564 foreach(QString groupUID, groupUIDs)
566 updateGroupCacheWithLatestEventsBatchSQL.append(
567 updateGroupCacheWithLatestEventsSQL
572 // Execute the statement in single-statement chunks thanks
573 // to QT's inability to call the SQLite function supporting
574 // multiple statements
576 QSqlQuery * setLatestEventInGroupCacheSQL(new QSqlQuery( db ));
577 if(NULL != setLatestEventInGroupCacheSQL)
579 setLatestEventInGroupCacheSQL->setForwardOnly( true );
583 QStringList statements = updateGroupCacheWithLatestEventsBatchSQL.trimmed().split(";", QString::SkipEmptyParts);
586 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
588 if ( setLatestEventInGroupCacheSQL->exec( *currentStatement ))
589 qDebug() << "Query OK, " << setLatestEventInGroupCacheSQL->numRowsAffected() << " rows affected.";
592 qDebug() << "Query Failed: " << *currentStatement;
593 throw std::exception();
597 qDebug() << "Committing.";
602 qDebug() << "Rolling back.";
607 qDebug() << "Unable to start transaction.";
613 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
614 qDebug() << "Query: " << groupUIDListSQL;
618 qDebug() << "Closing.";
620 QSqlDatabase::removeDatabase( "QSQLITE" );
626 QDebug operator<<(QDebug dbg, RTComElEvent &event)
628 dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
629 dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
630 dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
631 dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
632 dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
633 dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time).toUTC() << "\n";
634 dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time).toUTC() << "\n";
635 dbg.nospace() << "\tend-time:\t" << QDateTime::fromTime_t(event.fld_end_time).toUTC() << "\n";
636 dbg.nospace() << "\tis-read:\t" << (event.fld_is_read ? "true" : "false") << "\n";
637 dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
638 dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
639 dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
640 dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
641 dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
642 dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
643 dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
644 dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
645 dbg.nospace() << "\tremote-ebid:\t" << event.fld_remote_ebook_uid << "\n";
646 dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
647 dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
648 dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";
653 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
655 dbg.nospace() << "Event-id:\t" << attachment.event_id << "\n";
656 dbg.nospace() << "Path:\t" << attachment.path << "\n";
657 dbg.nospace() << "Desc:\t" << attachment.desc << "\n";
662 QDebug operator<<(QDebug dbg, GList &attachments)
664 dbg.nospace() << "Attachments" << "\n";
666 for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
668 qDebug() << *(RTComElAttachment*)attachment->data;
671 dbg.nospace() << "\n";
676 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
678 dbg.nospace() << "Attachments" << "\n";
680 foreach(RTComElAttachment *attachment, attachments)
681 dbg.nospace() << *attachment << "\n";
683 dbg.nospace() << "\n";