c34a8dfe22af4faac5538cc4b30d5b9344bdf08d
[qwerkisync] / DBBackends / RtcomEventLogger.cpp
1 /*
2  * Copyright (C) 2011, Jamie Thompson
3  *
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.
8  *
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.
13  *
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/>.
17  */
18
19 #include "RtcomEventLogger.h"
20
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"
27 #include "Settings.h"
28
29 #include <QDebug>
30 #include <QMutex>
31 #include <QWaitCondition>
32
33 // For reindexing
34 #include <QDir>
35 #include <QPair>
36 #include <QStringList>
37 #include <QtSql/QSqlDatabase>
38 #include <QtSql/QSqlQuery>
39 #include <QVariant>
40
41 #include <uuid/uuid.h>
42
43 #include <rtcom-eventlogger/event.h>
44 #include <rtcom-eventlogger/eventlogger.h>
45
46 #include <stdexcept>
47
48 using namespace DBBackends;
49 using namespace EventTypes;
50
51 QDebug operator<<(QDebug, RTComElEvent &);
52 QDebug operator<<(QDebug, RTComElAttachment &);
53 QDebug operator<<(QDebug, GList &);
54 QDebug operator<<(QDebug, QList<RTComElAttachment*> &);
55
56 RtcomEventLogger::RtcomEventLogger(const Settings &settings) :
57         m_Settings(settings)
58 {
59         RTComEl *el(rtcom_el_new());
60         if(NULL != el)
61         {
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"));
67
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);
72
73                 g_object_unref(el);
74         }
75         else
76                 qDebug() << "Failed to create event logger.";
77 }
78
79 RtcomEventLogger::RtcomEventLogger(const Settings &settings, const EventTypes::RtcomEvent &event) :
80         m_Settings(settings)
81 {
82 }
83
84 void RtcomEventLogger::Process(EventProcessors::iEventProcessor &processor)
85 {
86         // Initialise the event logger
87         RTComEl *el = rtcom_el_new();
88         if(NULL != el)
89         {
90                 foreach(eEventTypes service, m_ServiceIDs.keys())
91                         ProcessService(processor, service, *el);
92
93                 g_object_unref(el);
94         }
95         else
96                 qDebug() << "Failed to create event logger.";
97 }
98
99 void RtcomEventLogger::ProcessService(EventProcessors::iEventProcessor &processor, const EventTypes::eEventTypes service, const RTComEl &el)
100 {
101         RTComEl *el_nonconst(const_cast<RTComEl *>(&el));
102
103         bool incoming = CurrentSettings().ShouldProcess( Settings::INCOMING, service);
104         bool outgoing = CurrentSettings().ShouldProcess( Settings::OUTGOING, service);
105
106         if(incoming || outgoing)
107         {
108                 // Initialise a query
109                 RTComElQuery *query = rtcom_el_query_new(el_nonconst);
110                 if(query != NULL)
111                 {
112                         // Prepare it...
113                         bool prepared = false;
114                         if(incoming && outgoing)
115                         {
116                                 prepared = rtcom_el_query_prepare(query,
117                                         "service-id",
118                                         m_ServiceIDs.value(service),
119                                         RTCOM_EL_OP_EQUAL,
120
121                                         NULL);
122                         }
123                         else
124                         {
125                                 prepared = rtcom_el_query_prepare(query,
126                                         "service-id",
127                                         m_ServiceIDs.value(service),
128                                         RTCOM_EL_OP_EQUAL,
129
130                                         "outgoing",
131                                         incoming ? 0 : 1,
132                                         RTCOM_EL_OP_EQUAL,
133
134                                         NULL);
135                         }
136
137                         qDebug() << "SQL:\n" << rtcom_el_query_get_sql(query);
138
139                         if(prepared)
140                         {
141                                 RTComElIter *it = rtcom_el_get_events(el_nonconst, query);
142                                 if(it != NULL)
143                                 {
144                                         if(rtcom_el_iter_first(it))
145                                         {
146                                                 int eventCount = 0;
147                                                 qDebug() << "Getting event count...";
148                                                 while(rtcom_el_iter_next(it))
149                                                         ++eventCount;
150
151                                                 // Reset the iterator and grab the actual values
152                                                 qDebug() << "Resetting iterator...";
153                                                 g_object_unref(it);
154                                                 it = rtcom_el_get_events(el_nonconst, query);
155                                                 if(it != NULL)
156                                                 {
157                                                         if(rtcom_el_iter_first(it))
158                                                         {
159                                                                 int idx = 0;
160                                                                 qDebug() << "Getting events...";
161                                                                 do
162                                                                 {
163                                                                         ++idx;
164                                                                         qDebug() << "Event #" << idx;
165
166                                                                         RTComElEvent revent;
167                                                                         memset(&revent, 0, sizeof(revent));
168
169                                                                         if(rtcom_el_iter_get_full(it, &revent))
170                                                                         {
171                                                                                 qDebug() << revent;
172
173                                                                                 QList<RTComElAttachment *> rattachments;
174                                                                                 RTComElAttachIter *at_it = rtcom_el_iter_get_attachments(it);
175                                                                                 if(at_it != NULL)
176                                                                                 {
177                                                                                         qDebug() << "Attachments OK";
178                                                                                         if(rtcom_el_attach_iter_first(at_it))
179                                                                                         {
180                                                                                                 qDebug() << "Getting events...";
181
182                                                                                                 do
183                                                                                                 {
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));
189                                                                                         }
190                                                                                 }
191
192                                                                                 EventTypes::iEvent *const newEvent(CreateEvent(revent, rattachments));
193                                                                                 processor.Process(*newEvent);
194                                                                                 delete newEvent;
195
196                                                                                 processor.EmitEventProcessed(idx, eventCount);
197                                                                         }
198
199                                                                         rtcom_el_event_free_contents(&revent);
200                                                                 }
201                                                                 while(rtcom_el_iter_next(it));
202                                                                 qDebug() << "...all events retrieved.";
203                                                         }
204                                                 }
205                                                 else
206                                                         qDebug() << "Failed to reset iterator";
207                                         }
208                                         else
209                                                 qDebug() << "Failed to start iterator";
210                                 }
211                                 else
212                                         qDebug() << "Failed to get iterator. Do you have any events?";
213                         }
214                         else
215                                 qDebug() << "Failed to prepare the query.";
216
217                         g_object_unref(query);
218                 }
219                 else
220                         qDebug() << "Failed to create query.";
221         }
222         else
223                 qDebug() << "Nothing to do for " << m_ServiceIDs.value(service);
224 }
225
226 EventTypes::iEvent *const RtcomEventLogger::CreateEvent(RTComElEvent &revent, QList<RTComElAttachment*> &rattachments)
227 {
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);
230
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);
233
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);
236
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);
239
240         return NULL;
241 }
242
243 void RtcomEventLogger::PreInsert()
244 {
245         m_TriggerDisabler = new RtcomEventLoggerComponents::TriggerDisabler(CurrentSettings());
246 }
247
248 void RtcomEventLogger::Insert(EventTypes::iEvent &event, const NumberToNameLookup &numberToNameLookup)
249 {
250         if(EventTypes::RtcomEvent *rtcomEvent = dynamic_cast<EventTypes::RtcomEvent *>(&event))
251         {
252                 const uint UUID_STR_LEN(36);
253
254                 _RTComEl *el(rtcom_el_new());
255                 if(NULL != el)
256                 {
257                         // Convert our objects into RTCom structs
258                         RTComElEvent *revent(rtcomEvent->toRTComEvent(numberToNameLookup));
259                         GList *rattachments(event.Attachments().toRTComAttachments());
260
261                         GError *error(NULL);
262
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));
265                         uuid_t uuid;
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;
271
272                         qDebug() << "Inserting event:";
273                         qDebug() << *revent;
274                         qDebug() << *rattachments;
275
276                         // Add the event
277                         QDateTime startTime(QDateTime::currentDateTimeUtc());
278                         int newEventID(-1);
279                         int currentBackoffInMillisecs(5);
280                         while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
281                                   && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 10000)
282                         {
283                                 if(error != NULL)
284                                 {
285                                         qDebug() << "err: " << error->message;
286                                         g_error_free(error);
287                                         error = NULL;
288                                 }
289
290                                 // Don't hammer the DB when there's an error. Give it literally just a moment before retrying.
291                                 QMutex mutex;
292                                 mutex.lock();
293
294                                 QWaitCondition waitCondition;
295                                 waitCondition.wait(&mutex, currentBackoffInMillisecs);
296
297                                 mutex.unlock();
298
299                                 // Exponential backoff...
300                                 currentBackoffInMillisecs *= 2;
301                         }
302
303                         if(-1 == newEventID)
304                         {
305                                 qDebug() << "Unable to insert event due to error.";
306                                 qDebug() << *revent << "\n";
307                         }
308                         else
309                         {
310                                 qDebug() << "new id: " << newEventID;
311                                 InsertedIDs().append(newEventID);
312                         }
313
314                         // Release the attachments
315                         g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
316                         g_list_free (rattachments);
317
318                         rtcom_el_event_free_contents(revent);
319                         rtcom_el_event_free(revent);
320                 }
321                 else
322                         qDebug() << "Unable to initalise eventlogger for insertion.";
323
324                 g_object_unref(el);
325         }
326
327         return;
328 }
329
330 void RtcomEventLogger::PostInsert()
331 {
332         // Reorder the DB IDs as Nokia are guilty of both premature
333         // optimisation as well as closed source UIs...
334         Reindex();
335
336         delete m_TriggerDisabler;
337 }
338
339 void RtcomEventLogger::ClearInsertedIDs()
340 {
341         InsertedIDs().clear();
342 }
343
344 // Reorder the DB IDs as Nokia are guilty of both premature
345 // optimisation as well as closed source UIs...
346 void RtcomEventLogger::Reindex()
347 {
348         // Set up the database connection...
349         QSqlDatabase db(QSqlDatabase::addDatabase("QSQLITE"));
350
351         db.setDatabaseName(CurrentSettings().DBPath());
352         if(!db.open())
353         {
354                 throw std::runtime_error("Cannot open database: Unable to establish database connection");
355         }
356         else
357         {
358                 // Reorder the evnts by their start time
359                 uint changesRequired(0);
360                 do
361                 {
362                         // Note the smallest event ID found, so we have a place to start.
363                         int min(0);
364
365                         // The required ID changes ( current, correct );
366                         QHash<int, int> mapping;
367
368                         // Grab the current records, and determine what changes need to
369                         // happen to get to the sorted results
370                         {
371                                 qDebug() << "DB Opened";
372
373                                 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
374
375                                 dbq1->setForwardOnly( true );
376                                 dbq2->setForwardOnly( true );
377
378                                 QString s1("SELECT id, event_type_id, start_time, end_time "
379                                                    " FROM Events");
380                                 QString s2("SELECT id, event_type_id, start_time, end_time "
381                                                    " FROM Events ORDER BY start_time ASC");
382
383                                 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
384                                 {
385                                         qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
386
387                                         while( dbq1->next() && dbq2->next())
388                                         {
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 >() );
393
394                                                 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
395                                                 //qDebug() << "( " << one << ", " << two << " )";
396
397                                                 if(two != one)
398                                                 {
399                                                         if(min == 0)
400                                                                 min = one;
401
402                                                         //qDebug() << "( " << one << ", " << two << " )";
403                                                         mapping.insert(one, two);
404                                                 }
405                                         }
406                                 }
407                                 else
408                                 {
409                                         qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
410                                         qDebug() << "Query1: " << s1;
411                                         qDebug() << "Query2: " << s1;
412                                 }
413
414                                 // Clear up database connections
415                                 if ( dbq1 != NULL )
416                                 {
417                                         qDebug() << "Cleaning up connection 1";
418
419                                         dbq1->finish();
420
421                                         delete dbq1;
422                                         dbq1 = NULL;
423                                 }
424
425                                 if ( dbq2 != NULL )
426                                 {
427                                         qDebug() << "Cleaning up connection 2";
428
429                                         dbq2->finish();
430
431                                         delete dbq2;
432                                         dbq2 = NULL;
433                                 }
434                         }
435
436                         QList<int> sequence;
437                         int val(min);
438                         sequence.append(0);
439                         sequence.append(val);
440                         //qDebug().nospace() << "val1: " << val << ", ";
441
442                         while((val = mapping[val]) && val != min)
443                         {
444                                 sequence.append(val);
445                                 //qDebug().nospace() << val << ", ";
446                         }
447                         sequence.append(0);
448
449                         //qDebug().nospace() << "seq: ";
450                         QList<QPair<int,int> > updates;
451                         int last(sequence.first());
452                         foreach(int seq, sequence)
453                         {
454                                 if(seq != last)
455                                 {
456                                         //qDebug().nospace() << seq << ", " << last << ", ";
457                                         updates.append(QPair<int,int>(seq, last));
458                                 }
459
460                                 last = seq;
461                         }
462
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();
466
467                         for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
468                         {
469                                 //qDebug().nospace() << (*it).first << ", " << (*it).second;
470                         }
471
472                         QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
473                         QString query;
474                         for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
475                         {
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)
478                                 {
479                                         query.append(
480                                                 curquery
481                                                         .arg((*currentUpdate).second)
482                                                         .arg((*currentUpdate).first)
483                                                         .arg((*currentTable))
484                                                         .arg((*currentTable) == "Events" ? "id" : "event_id")
485                                                 ).append("\n");
486
487                                         //qDebug().nospace() << (*it).first << ", " << (*it).second;
488                                 }
489                         }
490
491                         //qDebug() << query;
492
493                         QSqlQuery * UpdateQuery(new QSqlQuery( db ));
494                         if(UpdateQuery != NULL)
495                         {
496                                 UpdateQuery->setForwardOnly( true );
497
498                                 if(db.transaction())
499                                 {
500                                         QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
501                                         try
502                                         {
503                                                 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
504                                                 {
505                                                         if (!UpdateQuery->exec(*currentStatement))
506                                                         {
507                                                                 qDebug() << "Query Failed: " << *currentStatement;
508                                                                 throw std::exception();
509                                                         }
510                                                 }
511
512                                                 qDebug() << "Committing.";
513                                                 db.commit();
514                                         }
515                                         catch(...)
516                                         {
517                                                 qDebug() << "Rolling back.";
518                                                 db.rollback();
519                                         }
520                                 }
521                                 else
522                                         qDebug() << "Unable to start transaction.";
523                         }
524                 }while(changesRequired > 0);
525
526                 qDebug() << "Closing.";
527                 db.close();
528                 QSqlDatabase::removeDatabase( "QSQLITE" );
529         }
530
531         return;
532 }
533
534 QDebug operator<<(QDebug dbg, RTComElEvent &event)
535 {
536         dbg.nospace() << "\tid:\t\t" << event.fld_id << "\n";
537         dbg.nospace() << "\tservice_id:\t" << event.fld_service_id << "\n";
538         dbg.nospace() << "\tservice:\t" << event.fld_service << "\n";
539         dbg.nospace() << "\tevt_typ_id:\t" << event.fld_event_type_id << "\n";
540         dbg.nospace() << "\tevt_typ:\t" << event.fld_event_type << "\n";
541         dbg.nospace() << "\tstore-time:\t" << QDateTime::fromTime_t(event.fld_storage_time).toUTC() << "\n";
542         dbg.nospace() << "\tstart-time:\t" << QDateTime::fromTime_t(event.fld_start_time).toUTC() << "\n";
543         dbg.nospace() << "\tend-time:\t" << QDateTime::fromTime_t(event.fld_end_time).toUTC() << "\n";
544         dbg.nospace() << "\tis-read:\t" << (event.fld_is_read ? "true" : "false") << "\n";
545         dbg.nospace() << "\tdirection:\t" << (event.fld_outgoing ? "Outgoing" : "Incoming") << "\n";
546         dbg.nospace() << "\tflags:\t\t" << "0x" << QString::number(event.fld_flags, 16) << "\n";
547         dbg.nospace() << "\tbytes sent:\t" << event.fld_bytes_sent << "\n";
548         dbg.nospace() << "\tbytes recv:\t" << event.fld_bytes_received << "\n";
549         dbg.nospace() << "\tlocal-uid:\t" << event.fld_local_uid << "\n";
550         dbg.nospace() << "\tlocal-name:\t" << event.fld_local_name << "\n";
551         dbg.nospace() << "\tremote-uid:\t" << event.fld_remote_uid << "\n";
552         dbg.nospace() << "\tremote-name:\t" << event.fld_remote_name << "\n";
553         dbg.nospace() << "\tremote-ebid:\t" << event.fld_remote_ebook_uid << "\n";
554         dbg.nospace() << "\tchannel:\t\t" << event.fld_channel << "\n";
555         dbg.nospace() << "\tfree-text:\t" << event.fld_free_text << "\n";
556         dbg.nospace() << "\tgroup-uid:\t" << event.fld_group_uid << "\n";
557
558         return dbg;
559 }
560
561 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
562 {
563         dbg.nospace() << "Event-id:\t" << attachment.event_id << "\n";
564         dbg.nospace() << "Path:\t" << attachment.path << "\n";
565         dbg.nospace() << "Desc:\t" << attachment.desc << "\n";
566
567         return dbg;
568 }
569
570 QDebug operator<<(QDebug dbg, GList &attachments)
571 {
572         dbg.nospace() << "Attachments" << "\n";
573
574         for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
575         {
576                 qDebug() << *(RTComElAttachment*)attachment->data;
577         }
578
579         dbg.nospace() << "\n";
580
581         return dbg;
582 }
583
584 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
585 {
586         dbg.nospace() << "Attachments" << "\n";
587
588         foreach(RTComElAttachment *attachment, attachments)
589                 dbg.nospace() << *attachment << "\n";
590
591         dbg.nospace() << "\n";
592
593         return dbg;
594 }