Added pre and post procesing "event handlers" to the DB backends. Initially u
[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), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
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), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
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                         while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
280                                   && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 1000)
281                         {
282                                 if(error != NULL)
283                                 {
284                                         qDebug() << "err: " << error->message;
285                                         qDebug() << *revent << "\n";
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, 5);
296
297                                 mutex.unlock();
298                         }
299
300                         if(-1 == newEventID)
301                         {
302                                 qDebug() << "new id: " << newEventID;
303                                 InsertedIDs().append(newEventID);
304                         }
305                         else
306                                 qDebug() << "Unable to insert event due to error.";
307
308                         // Release the attachments
309                         g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
310                         g_list_free (rattachments);
311
312                         rtcom_el_event_free_contents(revent);
313                         rtcom_el_event_free(revent);
314                 }
315                 else
316                         qDebug() << "Unable to initalise eventlogger for insertion.";
317
318                 g_object_unref(el);
319         }
320
321         return;
322 }
323
324 void RtcomEventLogger::PostInsert()
325 {
326         // Reorder the DB IDs as Nokia are guilty of both premature
327         // optimisation as well as closed source UIs...
328         Reindex();
329
330         delete m_TriggerDisabler;
331 }
332
333 void RtcomEventLogger::ClearInsertedIDs()
334 {
335         InsertedIDs().clear();
336 }
337
338 // Reorder the DB IDs as Nokia are guilty of both premature
339 // optimisation as well as closed source UIs...
340 void RtcomEventLogger::Reindex()
341 {
342         // Set up the database connection...
343         QSqlDatabase db(QSqlDatabase::addDatabase( "QSQLITE" ));
344
345         db.setDatabaseName( QDir::homePath() + mk_DBPath );
346         if ( ! db.open() )
347         {
348                 throw std::runtime_error("Cannot open database: Unable to establish database connection");
349         }
350         else
351         {
352                 // Reorder the evnts by their start time
353                 uint changesRequired(0);
354                 do
355                 {
356                         // Note the smallest event ID found, so we have a place to start.
357                         int min(0);
358
359                         // The required ID changes ( current, correct );
360                         QHash<int, int> mapping;
361
362                         // Grab the current records, and determine what changes need to
363                         // happen to get to the sorted results
364                         {
365                                 qDebug() << "DB Opened";
366
367                                 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
368
369                                 dbq1->setForwardOnly( true );
370                                 dbq2->setForwardOnly( true );
371
372                                 QString s1("SELECT id, event_type_id, start_time, end_time "
373                                                    " FROM Events");
374                                 QString s2("SELECT id, event_type_id, start_time, end_time "
375                                                    " FROM Events ORDER BY start_time ASC");
376
377                                 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
378                                 {
379                                         qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
380
381                                         while( dbq1->next() && dbq2->next())
382                                         {
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 >() );
387
388                                                 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
389                                                 //qDebug() << "( " << one << ", " << two << " )";
390
391                                                 if(two != one)
392                                                 {
393                                                         if(min == 0)
394                                                                 min = one;
395
396                                                         qDebug() << "( " << one << ", " << two << " )";
397                                                         mapping.insert(one, two);
398                                                 }
399                                         }
400                                 }
401                                 else
402                                 {
403                                         qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
404                                         qDebug() << "Query1: " << s1;
405                                         qDebug() << "Query2: " << s1;
406                                 }
407
408                                 // Clear up database connections
409                                 if ( dbq1 != NULL )
410                                 {
411                                         qDebug() << "Cleaning up connection 1";
412
413                                         dbq1->finish();
414
415                                         delete dbq1;
416                                         dbq1 = NULL;
417                                 }
418
419                                 if ( dbq2 != NULL )
420                                 {
421                                         qDebug() << "Cleaning up connection 2";
422
423                                         dbq2->finish();
424
425                                         delete dbq2;
426                                         dbq2 = NULL;
427                                 }
428                         }
429
430                         QList<int> sequence;
431                         int val(min);
432                         sequence.append(0);
433                         sequence.append(val);
434                         qDebug().nospace() << "val1: " << val << ", ";
435
436                         while((val = mapping[val]) && val != min)
437                         {
438                                 sequence.append(val);
439                                 qDebug().nospace() << val << ", ";
440                         }
441                         sequence.append(0);
442
443                         qDebug().nospace() << "seq: ";
444                         QList<QPair<int,int> > updates;
445                         int last(sequence.first());
446                         foreach(int seq, sequence)
447                         {
448                                 if(seq != last)
449                                 {
450                                         qDebug().nospace() << seq << ", " << last << ", ";
451                                         updates.append(QPair<int,int>(seq, last));
452                                 }
453
454                                 last = seq;
455                         }
456
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();
460
461                         for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
462                         {
463                                 //qDebug().nospace() << (*it).first << ", " << (*it).second;
464                         }
465
466                         QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
467                         QString query;
468                         for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
469                         {
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)
472                                 {
473                                         query.append(
474                                                 curquery
475                                                         .arg((*currentUpdate).second)
476                                                         .arg((*currentUpdate).first)
477                                                         .arg((*currentTable))
478                                                         .arg((*currentTable) == "Events" ? "id" : "event_id")
479                                                 ).append("\n");
480
481                                         //qDebug().nospace() << (*it).first << ", " << (*it).second;
482                                 }
483                         }
484
485                         qDebug() << query;
486
487                         QSqlQuery * UpdateQuery(new QSqlQuery( db ));
488                         if(UpdateQuery != NULL)
489                         {
490                                 UpdateQuery->setForwardOnly( true );
491
492                                 if(db.transaction())
493                                 {
494                                         QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
495                                         try
496                                         {
497                                                 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
498                                                 {
499                                                         if ( UpdateQuery->exec( *currentStatement ))
500                                                                 qDebug() << "Query OK, " << UpdateQuery->numRowsAffected() << " rows affected.";
501                                                         else
502                                                         {
503                                                                 qDebug() << "Query Failed: " << *currentStatement;
504                                                                 throw std::exception();
505                                                         }
506                                                 }
507
508                                                 qDebug() << "Committing.";
509                                                 db.commit();
510                                         }
511                                         catch(...)
512                                         {
513                                                 qDebug() << "Rolling back.";
514                                                 db.rollback();
515                                         }
516                                 }
517                                 else
518                                         qDebug() << "Unable to start transaction.";
519                         }
520                 }while(changesRequired > 0);
521
522                 // Update the group cache so the last events are correct
523                 {
524                         qDebug() << "Updating most recent events.";
525
526                         // Grab group UIDs from group cache
527                         QSqlQuery * dbq(new QSqlQuery( db ));
528                         dbq->setForwardOnly( true );
529
530                         const char * groupUIDListSQL("SELECT group_uid FROM GroupCache");
531                         if (dbq->exec(groupUIDListSQL))
532                         {
533                                 qDebug() << "Query OK, " << dbq->numRowsAffected() << " rows affected.";
534                                 qDebug() << "GroupUIDs:";
535
536                                 QSet<QString> groupUIDs;
537                                 while( dbq->next() )
538                                 {
539                                         QString groupUID(dbq->value(0).value<QString>());
540
541                                         qDebug() << groupUID;
542                                         groupUIDs.insert(groupUID);
543                                 }
544
545                                 // Iterate over group UIDS
546                                 if(groupUIDs.count() > 0)
547                                 {
548                                         // Build a batch statement to update every group with
549                                         // the most recent event
550
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)
561                                         {
562                                                 updateGroupCacheWithLatestEventsBatchSQL.append(
563                                                         updateGroupCacheWithLatestEventsSQL
564                                                         .arg(groupUID)
565                                                         ).append("\n");
566                                         }
567
568                                         // Execute the statement in single-statement chunks thanks
569                                         // to QT's inability to call the SQLite function supporting
570                                         // multiple statements
571
572                                         QSqlQuery * setLatestEventInGroupCacheSQL(new QSqlQuery( db ));
573                                         if(NULL != setLatestEventInGroupCacheSQL)
574                                         {
575                                                 setLatestEventInGroupCacheSQL->setForwardOnly( true );
576
577                                                 if(db.transaction())
578                                                 {
579                                                         QStringList statements = updateGroupCacheWithLatestEventsBatchSQL.trimmed().split(";", QString::SkipEmptyParts);
580                                                         try
581                                                         {
582                                                                 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
583                                                                 {
584                                                                         if ( setLatestEventInGroupCacheSQL->exec( *currentStatement ))
585                                                                                 qDebug() << "Query OK, " << setLatestEventInGroupCacheSQL->numRowsAffected() << " rows affected.";
586                                                                         else
587                                                                         {
588                                                                                 qDebug() << "Query Failed: " << *currentStatement;
589                                                                                 throw std::exception();
590                                                                         }
591                                                                 }
592
593                                                                 qDebug() << "Committing.";
594                                                                 db.commit();
595                                                         }
596                                                         catch(...)
597                                                         {
598                                                                 qDebug() << "Rolling back.";
599                                                                 db.rollback();
600                                                         }
601                                                 }
602                                                 else
603                                                         qDebug() << "Unable to start transaction.";
604                                         }
605                                 }
606                         }
607                         else
608                         {
609                                 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
610                                 qDebug() << "Query: " << groupUIDListSQL;
611                         }
612                 }
613
614                 qDebug() << "Closing.";
615                 db.close();
616                 QSqlDatabase::removeDatabase( "QSQLITE" );
617         }
618
619         return;
620 }
621
622 QDebug operator<<(QDebug dbg, RTComElEvent &event)
623 {
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";
644
645         return dbg;
646 }
647
648 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
649 {
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";
653
654         return dbg;
655 }
656
657 QDebug operator<<(QDebug dbg, GList &attachments)
658 {
659         dbg.nospace() << "Attachments" << "\n";
660
661         for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
662         {
663                 qDebug() << *(RTComElAttachment*)attachment->data;
664         }
665
666         dbg.nospace() << "\n";
667
668         return dbg;
669 }
670
671 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
672 {
673         dbg.nospace() << "Attachments" << "\n";
674
675         foreach(RTComElAttachment *attachment, attachments)
676                 dbg.nospace() << *attachment << "\n";
677
678         dbg.nospace() << "\n";
679
680         return dbg;
681 }