Added parsing of CSV-formatted Symbian event logs
[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 "Settings.h"
27
28 #include <QDebug>
29 #include <QMutex>
30 #include <QWaitCondition>
31
32 // For reindexing
33 #include <QDir>
34 #include <QPair>
35 #include <QStringList>
36 #include <QtSql/QSqlDatabase>
37 #include <QtSql/QSqlQuery>
38 #include <QVariant>
39
40 #include <uuid/uuid.h>
41
42 #include <rtcom-eventlogger/event.h>
43 #include <rtcom-eventlogger/eventlogger.h>
44
45 #include <stdexcept>
46
47 using namespace DBBackends;
48 using namespace EventTypes;
49
50 QDebug operator<<(QDebug, RTComElEvent &);
51 QDebug operator<<(QDebug, RTComElAttachment &);
52 QDebug operator<<(QDebug, GList &);
53 QDebug operator<<(QDebug, QList<RTComElAttachment*> &);
54
55 RtcomEventLogger::RtcomEventLogger(const Settings &settings) :
56         m_Settings(settings), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
57 {
58         RTComEl *el(rtcom_el_new());
59         if(NULL != el)
60         {
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"));
66
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);
71
72                 g_object_unref(el);
73         }
74         else
75                 qDebug() << "Failed to create event logger.";
76 }
77
78 RtcomEventLogger::RtcomEventLogger(const Settings &settings, const EventTypes::RtcomEvent &event) :
79         m_Settings(settings), mk_DBPath("/.rtcom-eventlogger/el-v1.db")
80 {
81 }
82
83 void RtcomEventLogger::Process(EventProcessors::iEventProcessor &processor)
84 {
85         // Initialise the event logger
86         RTComEl *el = rtcom_el_new();
87         if(NULL != el)
88         {
89                 foreach(eEventTypes service, m_ServiceIDs.keys())
90                         ProcessService(processor, service, *el);
91
92                 g_object_unref(el);
93         }
94         else
95                 qDebug() << "Failed to create event logger.";
96 }
97
98 void RtcomEventLogger::ProcessService(EventProcessors::iEventProcessor &processor, const EventTypes::eEventTypes service, const RTComEl &el)
99 {
100         RTComEl *el_nonconst(const_cast<RTComEl *>(&el));
101
102         bool incoming = CurrentSettings().ShouldProcess( Settings::TYPE_RECIEVED, service);
103         bool outgoing = CurrentSettings().ShouldProcess( Settings::TYPE_SENT, service);
104
105         if(incoming || outgoing)
106         {
107                 // Initialise a query
108                 RTComElQuery *query = rtcom_el_query_new(el_nonconst);
109                 if(query != NULL)
110                 {
111                         // Prepare it...
112                         bool prepared = false;
113                         if(incoming && outgoing)
114                         {
115                                 prepared = rtcom_el_query_prepare(query,
116                                         "service-id",
117                                         m_ServiceIDs.value(service),
118                                         RTCOM_EL_OP_EQUAL,
119
120                                         NULL);
121                         }
122                         else
123                         {
124                                 prepared = rtcom_el_query_prepare(query,
125                                         "service-id",
126                                         m_ServiceIDs.value(service),
127                                         RTCOM_EL_OP_EQUAL,
128
129                                         "outgoing",
130                                         incoming ? 0 : 1,
131                                         RTCOM_EL_OP_EQUAL,
132
133                                         NULL);
134                         }
135
136                         qDebug() << "SQL:\n" << rtcom_el_query_get_sql(query);
137
138                         if(prepared)
139                         {
140                                 RTComElIter *it = rtcom_el_get_events(el_nonconst, query);
141                                 if(it != NULL)
142                                 {
143                                         if(rtcom_el_iter_first(it))
144                                         {
145                                                 int eventCount = 0;
146                                                 qDebug() << "Getting event count...";
147                                                 while(rtcom_el_iter_next(it))
148                                                         ++eventCount;
149
150                                                 // Reset the iterator and grab the actual values
151                                                 qDebug() << "Resetting iterator...";
152                                                 g_object_unref(it);
153                                                 it = rtcom_el_get_events(el_nonconst, query);
154                                                 if(it != NULL)
155                                                 {
156                                                         if(rtcom_el_iter_first(it))
157                                                         {
158                                                                 int idx = 0;
159                                                                 qDebug() << "Getting events...";
160                                                                 do
161                                                                 {
162                                                                         ++idx;
163                                                                         qDebug() << "Event #" << idx;
164
165                                                                         RTComElEvent revent;
166                                                                         memset(&revent, 0, sizeof(revent));
167
168                                                                         if(rtcom_el_iter_get_full(it, &revent))
169                                                                         {
170                                                                                 qDebug() << revent;
171
172                                                                                 QList<RTComElAttachment *> rattachments;
173                                                                                 RTComElAttachIter *at_it = rtcom_el_iter_get_attachments(it);
174                                                                                 if(it != NULL)
175                                                                                 {
176                                                                                         qDebug() << "Attachments OK";
177                                                                                         if(rtcom_el_attach_iter_first(at_it))
178                                                                                         {
179                                                                                                 qDebug() << "Getting events...";
180
181                                                                                                 do
182                                                                                                 {
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));
188                                                                                         }
189                                                                                 }
190
191                                                                                 EventTypes::iEvent *const newEvent(CreateEvent(revent, rattachments));
192                                                                                 processor.Process(*newEvent);
193                                                                                 delete newEvent;
194
195                                                                                 processor.EmitEventProcessed(idx, eventCount);
196                                                                         }
197
198                                                                         rtcom_el_event_free_contents(&revent);
199                                                                 }
200                                                                 while(rtcom_el_iter_next(it));
201                                                                 qDebug() << "...all events retrieved.";
202                                                         }
203                                                 }
204                                                 else
205                                                         qDebug() << "Failed to reset iterator";
206                                         }
207                                         else
208                                                 qDebug() << "Failed to start iterator";
209                                 }
210                                 else
211                                         qDebug() << "Failed to get iterator. Do you have any events?";
212                         }
213                         else
214                                 qDebug() << "Failed to prepare the query.";
215
216                         g_object_unref(query);
217                 }
218                 else
219                         qDebug() << "Failed to create query.";
220         }
221         else
222                 qDebug() << "Nothing to do for " << m_ServiceIDs.value(service);
223 }
224
225 EventTypes::iEvent *const RtcomEventLogger::CreateEvent(RTComElEvent &revent, QList<RTComElAttachment*> &rattachments)
226 {
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);
229
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);
232
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);
235
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);
238
239         return NULL;
240 }
241
242 void RtcomEventLogger::Insert(EventTypes::iEvent &event, const NumberToNameLookup &numberToNameLookup)
243 {
244         if(EventTypes::RtcomEvent *rtcomEvent = dynamic_cast<EventTypes::RtcomEvent *>(&event))
245         {
246                 const uint UUID_STR_LEN(36);
247
248                 _RTComEl *el(rtcom_el_new());
249                 if(NULL != el)
250                 {
251                         // Convert our objects into RTCom structs
252                         RTComElEvent *revent(rtcomEvent->toRTComEvent(numberToNameLookup));
253                         GList *rattachments(event.Attachments().toRTComAttachments());
254
255                         GError *error(NULL);
256
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));
259                         uuid_t uuid;
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;
265
266                         qDebug() << "Inserting event:";
267                         qDebug() << *revent;
268                         qDebug() << *rattachments;
269
270                         // Add the event
271                         QDateTime startTime(QDateTime::currentDateTimeUtc());
272                         int newEventID(-1);
273                         while(((newEventID = rtcom_el_add_event_full(el, revent, rheaders, rattachments, &error)) == -1)
274                                   && startTime.msecsTo(QDateTime::currentDateTimeUtc()) < 1000)
275                         {
276                                 if(error != NULL)
277                                 {
278                                         qDebug() << "err: " << error->message;
279                                         qDebug() << *revent << "\n";
280                                         g_error_free(error);
281                                         error = NULL;
282                                 }
283
284                                 // Don't hammer the DB when there's an error. Give it literally just a moment before retrying.
285                                 QMutex mutex;
286                                 mutex.lock();
287
288                                 QWaitCondition waitCondition;
289                                 waitCondition.wait(&mutex, 5);
290
291                                 mutex.unlock();
292                         }
293
294                         if(-1 == newEventID)
295                         {
296                                 qDebug() << "new id: " << newEventID;
297                                 InsertedIDs().append(newEventID);
298                         }
299                         else
300                                 qDebug() << "Unable to insert event due to error.";
301
302                         // Release the attachments
303                         g_list_foreach (rattachments, (GFunc) rtcom_el_free_attachment, NULL);
304                         g_list_free (rattachments);
305
306                         rtcom_el_event_free_contents(revent);
307                         rtcom_el_event_free(revent);
308                 }
309                 else
310                         qDebug() << "Unable to initalise eventlogger for insertion.";
311
312                 g_object_unref(el);
313         }
314
315         return;
316 }
317
318 void RtcomEventLogger::PostInsert()
319 {
320         // Reorder the DB IDs as Nokia are guilty of both premature
321         // optimisation as well as closed source UIs...
322         Reindex();
323 }
324
325 void RtcomEventLogger::ClearInsertedIDs()
326 {
327         InsertedIDs().clear();
328 }
329
330 // Reorder the DB IDs as Nokia are guilty of both premature
331 // optimisation as well as closed source UIs...
332 void RtcomEventLogger::Reindex()
333 {
334         // Set up the database connection...
335         QSqlDatabase db(QSqlDatabase::addDatabase( "QSQLITE" ));
336
337         db.setDatabaseName( QDir::homePath() + mk_DBPath );
338         if ( ! db.open() )
339         {
340                 throw std::runtime_error("Cannot open database: Unable to establish database connection");
341         }
342         else
343         {
344                 // Reorder the evnts by their start time
345                 uint changesRequired(0);
346                 do
347                 {
348                         // Note the smallest event ID found, so we have a place to start.
349                         int min(0);
350
351                         // The required ID changes ( current, correct );
352                         QHash<int, int> mapping;
353
354                         // Grab the current records, and determine what changes need to
355                         // happen to get to the sorted results
356                         {
357                                 qDebug() << "DB Opened";
358
359                                 QSqlQuery * dbq1(new QSqlQuery( db )), * dbq2(new QSqlQuery( db ));
360
361                                 dbq1->setForwardOnly( true );
362                                 dbq2->setForwardOnly( true );
363
364                                 QString s1("SELECT id, event_type_id, start_time, end_time "
365                                                    " FROM Events");
366                                 QString s2("SELECT id, event_type_id, start_time, end_time "
367                                                    " FROM Events ORDER BY start_time ASC");
368
369                                 if ( dbq1->exec( s1 ) && dbq2->exec( s2 ))
370                                 {
371                                         qDebug() << "Query OK, " << dbq1->numRowsAffected() << " & " << dbq2->numRowsAffected() << " rows affected.";
372
373                                         while( dbq1->next() && dbq2->next())
374                                         {
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 >() );
379
380                                                 //qDebug() << "Event: " << type << ", " << startTime << ", " << endTime << "";
381                                                 //qDebug() << "( " << one << ", " << two << " )";
382
383                                                 if(two != one)
384                                                 {
385                                                         if(min == 0)
386                                                                 min = one;
387
388                                                         qDebug() << "( " << one << ", " << two << " )";
389                                                         mapping.insert(one, two);
390                                                 }
391                                         }
392                                 }
393                                 else
394                                 {
395                                         qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
396                                         qDebug() << "Query1: " << s1;
397                                         qDebug() << "Query2: " << s1;
398                                 }
399
400                                 // Clear up database connections
401                                 if ( dbq1 != NULL )
402                                 {
403                                         qDebug() << "Cleaning up connection 1";
404
405                                         dbq1->finish();
406
407                                         delete dbq1;
408                                         dbq1 = NULL;
409                                 }
410
411                                 if ( dbq2 != NULL )
412                                 {
413                                         qDebug() << "Cleaning up connection 2";
414
415                                         dbq2->finish();
416
417                                         delete dbq2;
418                                         dbq2 = NULL;
419                                 }
420                         }
421
422                         QList<int> sequence;
423                         int val(min);
424                         sequence.append(0);
425                         sequence.append(val);
426                         qDebug().nospace() << "val1: " << val << ", ";
427
428                         while((val = mapping[val]) && val != min)
429                         {
430                                 sequence.append(val);
431                                 qDebug().nospace() << val << ", ";
432                         }
433                         sequence.append(0);
434
435                         qDebug().nospace() << "seq: ";
436                         QList<QPair<int,int> > updates;
437                         int last(sequence.first());
438                         foreach(int seq, sequence)
439                         {
440                                 if(seq != last)
441                                 {
442                                         qDebug().nospace() << seq << ", " << last << ", ";
443                                         updates.append(QPair<int,int>(seq, last));
444                                 }
445
446                                 last = seq;
447                         }
448
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();
452
453                         for( QList<QPair<int,int> >::const_iterator it(updates.constBegin()); it != updates.constEnd(); ++it)
454                         {
455                                 //qDebug().nospace() << (*it).first << ", " << (*it).second;
456                         }
457
458                         QList<QString> tables = QList<QString>() << "Events" << "Attachments" << "Headers" << "GroupCache";
459                         QString query;
460                         for( QList<QString>::const_iterator currentTable(tables.constBegin()); currentTable != tables.constEnd(); ++currentTable)
461                         {
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)
464                                 {
465                                         query.append(
466                                                 curquery
467                                                         .arg((*currentUpdate).second)
468                                                         .arg((*currentUpdate).first)
469                                                         .arg((*currentTable))
470                                                         .arg((*currentTable) == "Events" ? "id" : "event_id")
471                                                 ).append("\n");
472
473                                         //qDebug().nospace() << (*it).first << ", " << (*it).second;
474                                 }
475                         }
476
477                         qDebug() << query;
478
479                         QSqlQuery * UpdateQuery(new QSqlQuery( db ));
480                         if(UpdateQuery != NULL)
481                         {
482                                 UpdateQuery->setForwardOnly( true );
483
484                                 if(db.transaction())
485                                 {
486                                         QStringList statements = query.trimmed().split(";", QString::SkipEmptyParts);
487                                         try
488                                         {
489                                                 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
490                                                 {
491                                                         if ( UpdateQuery->exec( *currentStatement ))
492                                                                 qDebug() << "Query OK, " << UpdateQuery->numRowsAffected() << " rows affected.";
493                                                         else
494                                                         {
495                                                                 qDebug() << "Query Failed: " << *currentStatement;
496                                                                 throw std::exception();
497                                                         }
498                                                 }
499
500                                                 qDebug() << "Committing.";
501                                                 db.commit();
502                                         }
503                                         catch(...)
504                                         {
505                                                 qDebug() << "Rolling back.";
506                                                 db.rollback();
507                                         }
508                                 }
509                                 else
510                                         qDebug() << "Unable to start transaction.";
511                         }
512                 }while(changesRequired > 0);
513
514                 // Update the group cache so the last events are correct
515                 {
516                         qDebug() << "Updating most recent events.";
517
518                         // Grab group UIDs from group cache
519                         QSqlQuery * dbq(new QSqlQuery( db ));
520                         dbq->setForwardOnly( true );
521
522                         const char * groupUIDListSQL("SELECT group_uid FROM GroupCache");
523                         if (dbq->exec(groupUIDListSQL))
524                         {
525                                 qDebug() << "Query OK, " << dbq->numRowsAffected() << " rows affected.";
526                                 qDebug() << "GroupUIDs:";
527
528                                 QSet<QString> groupUIDs;
529                                 while( dbq->next() )
530                                 {
531                                         QString groupUID(dbq->value(0).value<QString>());
532
533                                         qDebug() << groupUID;
534                                         groupUIDs.insert(groupUID);
535                                 }
536
537                                 // Iterate over group UIDS
538                                 if(groupUIDs.count() > 0)
539                                 {
540                                         // Build a batch statement to update every group with
541                                         // the most recent event
542
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)
553                                         {
554                                                 updateGroupCacheWithLatestEventsBatchSQL.append(
555                                                         updateGroupCacheWithLatestEventsSQL
556                                                         .arg(groupUID)
557                                                         ).append("\n");
558                                         }
559
560                                         // Execute the statement in single-statement chunks thanks
561                                         // to QT's inability to call the SQLite function supporting
562                                         // multiple statements
563
564                                         QSqlQuery * setLatestEventInGroupCacheSQL(new QSqlQuery( db ));
565                                         if(NULL != setLatestEventInGroupCacheSQL)
566                                         {
567                                                 setLatestEventInGroupCacheSQL->setForwardOnly( true );
568
569                                                 if(db.transaction())
570                                                 {
571                                                         QStringList statements = updateGroupCacheWithLatestEventsBatchSQL.trimmed().split(";", QString::SkipEmptyParts);
572                                                         try
573                                                         {
574                                                                 for( QStringList::const_iterator currentStatement(statements.constBegin()); currentStatement != statements.constEnd(); ++currentStatement)
575                                                                 {
576                                                                         if ( setLatestEventInGroupCacheSQL->exec( *currentStatement ))
577                                                                                 qDebug() << "Query OK, " << setLatestEventInGroupCacheSQL->numRowsAffected() << " rows affected.";
578                                                                         else
579                                                                         {
580                                                                                 qDebug() << "Query Failed: " << *currentStatement;
581                                                                                 throw std::exception();
582                                                                         }
583                                                                 }
584
585                                                                 qDebug() << "Committing.";
586                                                                 db.commit();
587                                                         }
588                                                         catch(...)
589                                                         {
590                                                                 qDebug() << "Rolling back.";
591                                                                 db.rollback();
592                                                         }
593                                                 }
594                                                 else
595                                                         qDebug() << "Unable to start transaction.";
596                                         }
597                                 }
598                         }
599                         else
600                         {
601                                 qDebug() << "SQL EXEC Error: "<< "EXEC query failed";
602                                 qDebug() << "Query: " << groupUIDListSQL;
603                         }
604                 }
605
606                 qDebug() << "Closing.";
607                 db.close();
608                 QSqlDatabase::removeDatabase( "QSQLITE" );
609         }
610
611         return;
612 }
613
614 QDebug operator<<(QDebug dbg, RTComElEvent &event)
615 {
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";
636
637         return dbg;
638 }
639
640 QDebug operator<<(QDebug dbg, RTComElAttachment &attachment)
641 {
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";
645
646         return dbg;
647 }
648
649 QDebug operator<<(QDebug dbg, GList &attachments)
650 {
651         dbg.nospace() << "Attachments" << "\n";
652
653         for (GList *attachment(&attachments); NULL != attachment; attachment = attachment->next)
654         {
655                 qDebug() << *(RTComElAttachment*)attachment->data;
656         }
657
658         dbg.nospace() << "\n";
659
660         return dbg;
661 }
662
663 QDebug operator<<(QDebug dbg, QList<RTComElAttachment *> &attachments)
664 {
665         dbg.nospace() << "Attachments" << "\n";
666
667         foreach(RTComElAttachment *attachment, attachments)
668                 dbg.nospace() << *attachment << "\n";
669
670         dbg.nospace() << "\n";
671
672         return dbg;
673 }