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"
30 #include <QWaitCondition>
35 #include <QStringList>
36 #include <QtSql/QSqlDatabase>
37 #include <QtSql/QSqlQuery>
40 #include <uuid/uuid.h>
42 #include <rtcom-eventlogger/event.h>
43 #include <rtcom-eventlogger/eventlogger.h>
47 using namespace DBBackends;
48 using namespace EventTypes;
50 QDebug operator<<(QDebug, RTComElEvent &);
51 QDebug operator<<(QDebug, RTComElAttachment &);
52 QDebug operator<<(QDebug, GList &);
53 QDebug operator<<(QDebug, QList<RTComElAttachment*> &);
55 RtcomEventLogger::RtcomEventLogger(const Settings &settings) :
56 m_Settings(settings), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
58 RTComEl *el(rtcom_el_new());
61 // Grab the service IDs we want to work with
62 m_ServiceIDs.insert(EVENT_TYPE_CALL, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CALL"));
63 //m_ServiceIDs.insert(EVENT_TYPE_CHAT, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_CHAT"));
64 m_ServiceIDs.insert(EVENT_TYPE_SMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"));
65 //m_ServiceIDs.insert(EVENT_TYPE_MMS, rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_MMS"));
67 // Remove any service IDs that weren't found
68 foreach(EventTypes::eEventTypes service, m_ServiceIDs.keys())
69 if(m_ServiceIDs.value(service) == -1)
70 m_ServiceIDs.remove(service);
75 qDebug() << "Failed to create event logger.";
78 RtcomEventLogger::RtcomEventLogger(const Settings &settings, const EventTypes::RtcomEvent &event) :
79 m_Settings(settings), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
83 void RtcomEventLogger::Process(EventProcessors::iEventProcessor &processor)
85 // Initialise the event logger
86 RTComEl *el = rtcom_el_new();
89 foreach(eEventTypes service, m_ServiceIDs.keys())
90 ProcessService(processor, service, *el);
95 qDebug() << "Failed to create event logger.";
98 void RtcomEventLogger::ProcessService(EventProcessors::iEventProcessor &processor, const EventTypes::eEventTypes service, const RTComEl &el)
100 RTComEl *el_nonconst(const_cast<RTComEl *>(&el));
102 bool incoming = CurrentSettings().ShouldProcess( Settings::TYPE_RECIEVED, service);
103 bool outgoing = CurrentSettings().ShouldProcess( Settings::TYPE_SENT, service);
105 if(incoming || outgoing)
107 // Initialise a query
108 RTComElQuery *query = rtcom_el_query_new(el_nonconst);
112 bool prepared = false;
113 if(incoming && outgoing)
115 prepared = rtcom_el_query_prepare(query,
117 m_ServiceIDs.value(service),
124 prepared = rtcom_el_query_prepare(query,
126 m_ServiceIDs.value(service),
136 qDebug() << "SQL:\n" << rtcom_el_query_get_sql(query);
140 RTComElIter *it = rtcom_el_get_events(el_nonconst, query);
143 if(rtcom_el_iter_first(it))
146 qDebug() << "Getting event count...";
147 while(rtcom_el_iter_next(it))
150 // Reset the iterator and grab the actual values
151 qDebug() << "Resetting iterator...";
153 it = rtcom_el_get_events(el_nonconst, query);
156 if(rtcom_el_iter_first(it))
159 qDebug() << "Getting events...";
163 qDebug() << "Event #" << idx;
166 memset(&revent, 0, sizeof(revent));
168 if(rtcom_el_iter_get_full(it, &revent))
172 QList<RTComElAttachment *> rattachments;
173 RTComElAttachIter *at_it = rtcom_el_iter_get_attachments(it);
176 qDebug() << "Attachments OK";
177 if(rtcom_el_attach_iter_first(at_it))
179 qDebug() << "Getting events...";
183 rattachments.append(rtcom_el_attach_iter_get(at_it));
184 qDebug() << "Attachment ID #" << rattachments.last()->id << endl;
185 qDebug() << "desc: " << rattachments.last()->desc << endl;
186 qDebug() << "path: " << rattachments.last()->path << endl;
187 }while(rtcom_el_attach_iter_next(at_it));
191 EventTypes::iEvent *const newEvent(CreateEvent(revent, rattachments));
192 processor.Process(*newEvent);
195 processor.EmitEventProcessed(idx, eventCount);
198 rtcom_el_event_free_contents(&revent);
200 while(rtcom_el_iter_next(it));
201 qDebug() << "...all events retrieved.";
205 qDebug() << "Failed to reset iterator";
208 qDebug() << "Failed to start iterator";
211 qDebug() << "Failed to get iterator. Do you have any events?";
214 qDebug() << "Failed to prepare the query.";
216 g_object_unref(query);
219 qDebug() << "Failed to create query.";
222 qDebug() << "Nothing to do for " << m_ServiceIDs.value(service);
225 EventTypes::iEvent *const RtcomEventLogger::CreateEvent(RTComElEvent &revent, QList<RTComElAttachment*> &rattachments)
227 if(m_ServiceIDs.contains(EVENT_TYPE_CALL) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CALL))
228 return new EventTypes::PhoneCall(CurrentSettings(), revent, rattachments);
230 //if(m_ServiceIDs.contains(EVENT_TYPE_CHAT) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_CHAT))
231 // return new EventTypes::Chat(CurrentSettings(), revent, rattachments);
233 if(m_ServiceIDs.contains(EVENT_TYPE_SMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_SMS))
234 return new EventTypes::SMS(CurrentSettings(), revent, rattachments);
236 //if(m_ServiceIDs.contains(EVENT_TYPE_MMS) && revent.fld_service_id == m_ServiceIDs.value(EVENT_TYPE_MMS))
237 // return new EventTypes::MMS(CurrentSettings(), revent, rattachments);
242 void RtcomEventLogger::Insert(EventTypes::iEvent &event, const NumberToNameLookup &numberToNameLookup)
244 if(EventTypes::RtcomEvent *rtcomEvent = dynamic_cast<EventTypes::RtcomEvent *>(&event))
246 const uint UUID_STR_LEN(36);
248 _RTComEl *el(rtcom_el_new());
251 // Convert our objects into RTCom structs
252 RTComElEvent *revent(rtcomEvent->toRTComEvent(numberToNameLookup));
253 GList *rattachments(event.Attachments().toRTComAttachments());
257 // Generate the headers for the event
258 GHashTable *rheaders(g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free));
260 char key[UUID_STR_LEN + 1];
261 uuid_generate_random(uuid);
262 uuid_unparse(uuid, key);
263 g_hash_table_insert(rheaders, g_strdup ("message-token"), key);
264 qDebug() << "headers: " << rheaders;
266 qDebug() << "Inserting event:";
268 qDebug() << *rattachments;
271 QDateTime startTime(QDateTime::currentDateTimeUtc());
273 while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
274 && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 1000)
278 qDebug() << "err: " << error->message;
279 qDebug() << *revent << "\n";
284 // Don't hammer the DB when there's an error. Give it literally just a moment before retrying.
288 QWaitCondition waitCondition;
289 waitCondition.wait(&mutex, 5);
296 qDebug() << "new id: " << newEventID;
297 InsertedIDs().append(newEventID);
300 qDebug() << "Unable to insert event due to error.";
302 // Release the attachments
303 g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
304 g_list_free (rattachments);
306 rtcom_el_event_free_contents(revent);
307 rtcom_el_event_free(revent);
310 qDebug() << "Unable to initalise eventlogger for insertion.";
318 void RtcomEventLogger::PostInsert()
320 // Reorder the DB IDs as Nokia are guilty of both premature
321 // optimisation as well as closed source UIs...
325 void RtcomEventLogger::ClearInsertedIDs()
327 InsertedIDs().clear();
330 // Reorder the DB IDs as Nokia are guilty of both premature
331 // optimisation as well as closed source UIs...
332 void RtcomEventLogger::Reindex()
334 // Set up the database connection...
335 QSqlDatabase db(QSqlDatabase::addDatabase( "QSQLITE" ));
337 db.setDatabaseName( QDir::homePath() + mk_DBPath );
340 throw std::runtime_error("Cannot open database: Unable to establish database connection");
344 // Reorder the evnts by their start time
345 uint changesRequired(0);
348 // Note the smallest event ID found, so we have a place to start.
351 // The required ID changes ( current, correct );
352 QHash<int, int> mapping;
354 // Grab the current records, and determine what changes need to
355 // happen to get to the sorted results
357 qDebug() << "DB Opened";
359 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
361 dbq1->setForwardOnly( true );
362 dbq2->setForwardOnly( true );
364 QString s1("SELECT id, event_type_id, start_time, end_time "
366 QString s2("SELECT id, event_type_id, start_time, end_time "
367 " FROM Events ORDER BY start_time ASC");
369 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
371 qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
373 while( dbq1->next() && dbq2->next())
375 int one (dbq1->value( 0 ).value< int >());
376 int two (dbq2->value( 0 ).value< int >());
377 //uint startTime( m_dbq->value( 1 ).value< uint >() );
378 //uint endTime( m_dbq->value( 2 ).value< uint >() );
380 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
381 //qDebug() << "( " << one << ", " << two << " )";
388 qDebug() << "( " << one << ", " << two << " )";
389 mapping.insert(one, two);
395 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
396 qDebug() << "Query1: " << s1;
397 qDebug() << "Query2: " << s1;
400 // Clear up database connections
403 qDebug() << "Cleaning up connection 1";
413 qDebug() << "Cleaning up connection 2";
425 sequence.append(val);
426 qDebug().nospace() << "val1: " << val << ", ";
428 while((val = mapping[val]) && val != min)
430 sequence.append(val);
431 qDebug().nospace() << val << ", ";
435 qDebug().nospace() << "seq: ";
436 QList<QPair<int,int> > updates;
437 int last(sequence.first());
438 foreach(int seq, sequence)
442 qDebug().nospace() << seq << ", " << last << ", ";
443 updates.append(QPair<int,int>(seq, last));
449 // Used to keep iterating until no changes are required.
450 // TODO: Shouldn't be required, but is. One to revisit later.
451 changesRequired = updates.count();
453 for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
455 //qDebug().nospace() << (*it).first << ", " << (*it).second;
458 QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
460 for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
462 QString curquery = "UPDATE %3 set %4 = %1 WHERE %4 = %2;";
463 for( QList<QPair<int,int> >::const_iterator currentUpdate(updates.constBegin()); currentUpdate != updates.constEnd(); ++currentUpdate)
467 .arg((*currentUpdate).second)
468 .arg((*currentUpdate).first)
469 .arg((*currentTable))
470 .arg((*currentTable) == "Events" ? "id" : "event_id")
473 //qDebug().nospace() << (*it).first << ", " << (*it).second;
479 QSqlQuery * UpdateQuery(new QSqlQuery( db ));
480 if(UpdateQuery != NULL)
482 UpdateQuery->setForwardOnly( true );
486 QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
489 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
491 if ( UpdateQuery->exec( *currentStatement ))
492 qDebug() << "Query OK, " << UpdateQuery->numRowsAffected() << " rows affected.";
495 qDebug() << "Query Failed: " << *currentStatement;
496 throw std::exception();
500 qDebug() << "Committing.";
505 qDebug() << "Rolling back.";
510 qDebug() << "Unable to start transaction.";
512 }while(changesRequired > 0);
514 // Update the group cache so the last events are correct
516 qDebug() << "Updating most recent events.";
518 // Grab group UIDs from group cache
519 QSqlQuery * dbq(new QSqlQuery( db ));
520 dbq->setForwardOnly( true );
522 const char * groupUIDListSQL("SELECT group_uid FROM GroupCache");
523 if (dbq->exec(groupUIDListSQL))
525 qDebug() << "Query OK, " << dbq->numRowsAffected() << " rows affected.";
526 qDebug() << "GroupUIDs:";
528 QSet<QString> groupUIDs;
531 QString groupUID(dbq->value(0).value<QString>());
533 qDebug() << groupUID;
534 groupUIDs.insert(groupUID);
537 // Iterate over group UIDS
538 if(groupUIDs.count() > 0)
540 // Build a batch statement to update every group with
541 // the most recent event
543 // Ignore 'data' failures (i.e. no events but present in the
544 // cache)- something else's been monkeying with the DB, and
545 // we can't account for everything.
546 const QString updateGroupCacheWithLatestEventsSQL(
547 "UPDATE OR IGNORE GroupCache SET event_id = "
548 "(SELECT id FROM events WHERE group_uid = \"%1\" "
549 " ORDER BY id DESC LIMIT 1)"
550 " WHERE group_uid = \"%1\";");
551 QString updateGroupCacheWithLatestEventsBatchSQL;
552 foreach(QString groupUID, groupUIDs)
554 updateGroupCacheWithLatestEventsBatchSQL.append(
555 updateGroupCacheWithLatestEventsSQL
560 // Execute the statement in single-statement chunks thanks
561 // to QT's inability to call the SQLite function supporting
562 // multiple statements
564 QSqlQuery * setLatestEventInGroupCacheSQL(new QSqlQuery( db ));
565 if(NULL != setLatestEventInGroupCacheSQL)
567 setLatestEventInGroupCacheSQL->setForwardOnly( true );
571 QStringList statements = updateGroupCacheWithLatestEventsBatchSQL.trimmed().split(";", QString::SkipEmptyParts);
574 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
576 if ( setLatestEventInGroupCacheSQL->exec( *currentStatement ))
577 qDebug() << "Query OK, " << setLatestEventInGroupCacheSQL->numRowsAffected() << " rows affected.";
580 qDebug() << "Query Failed: " << *currentStatement;
581 throw std::exception();
585 qDebug() << "Committing.";
590 qDebug() << "Rolling back.";
595 qDebug() << "Unable to start transaction.";
601 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
602 qDebug() << "Query: " << groupUIDListSQL;
606 qDebug() << "Closing.";
608 QSqlDatabase::removeDatabase( "QSQLITE" );
614 QDebug operator<<(QDebug dbg, RTComElEvent &event)
616 dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
617 dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
618 dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
619 dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
620 dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
621 dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time) << "\n";
622 dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time) << "\n";
623 dbg.nospace() << "\tend-time:\t\t" << QDateTime::fromTime_t(event.fld_end_time) << "\n";
624 dbg.nospace() << "\tis-read:\t\t" << (event.fld_is_read ? "true" : "false") << "\n";
625 dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
626 dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
627 dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
628 dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
629 dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
630 dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
631 dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
632 dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
633 dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
634 dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
635 dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";
640 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
642 dbg.nospace() << "Event-id:\t" << attachment.event_id << "\n";
643 dbg.nospace() << "Path:\t" << attachment.path << "\n";
644 dbg.nospace() << "Desc:\t" << attachment.desc << "\n";
649 QDebug operator<<(QDebug dbg, GList &attachments)
651 dbg.nospace() << "Attachments" << "\n";
653 for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
655 qDebug() << *(RTComElAttachment*)attachment->data;
658 dbg.nospace() << "\n";
663 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
665 dbg.nospace() << "Attachments" << "\n";
667 foreach(RTComElAttachment *attachment, attachments)
668 dbg.nospace() << *attachment << "\n";
670 dbg.nospace() << "\n";