~ubuntu-branches/ubuntu/saucy/kopete/saucy-proposed

« back to all changes in this revision

Viewing changes to plugins/history/historylogger.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-06-21 02:22:39 UTC
  • Revision ID: package-import@ubuntu.com-20130621022239-63l3zc8p0nf26pt6
Tags: upstream-4.10.80
ImportĀ upstreamĀ versionĀ 4.10.80

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    historylogger.cpp
 
3
 
 
4
    Copyright (c) 2003-2004 by Olivier Goffart        <ogoffart@kde.org>
 
5
 
 
6
    Kopete    (c) 2003-2004 by the Kopete developers  <kopete-devel@kde.org>
 
7
 
 
8
    *************************************************************************
 
9
    *                                                                       *
 
10
    * This program is free software; you can redistribute it and/or modify  *
 
11
    * it under the terms of the GNU General Public License as published by  *
 
12
    * the Free Software Foundation; either version 2 of the License, or     *
 
13
    * (at your option) any later version.                                   *
 
14
    *                                                                       *
 
15
    *************************************************************************
 
16
*/
 
17
 
 
18
#include "historylogger.h"
 
19
 
 
20
#include <QtCore/QRegExp>
 
21
#include <QtCore/QFile>
 
22
#include <QtCore/QDir>
 
23
#include <QtCore/QDateTime>
 
24
#include <QtCore/QTimer>
 
25
#include <QtCore/QTextStream>
 
26
#include <QtCore/QList>
 
27
#include <QtCore/QDate>
 
28
#include <QtGui/QTextDocument>
 
29
 
 
30
#include <kdebug.h>
 
31
#include <kstandarddirs.h>
 
32
#include <ksavefile.h>
 
33
 
 
34
#include "kopeteglobal.h"
 
35
#include "kopetecontact.h"
 
36
#include "kopeteprotocol.h"
 
37
#include "kopeteaccount.h"
 
38
#include "kopetemetacontact.h"
 
39
#include "kopetechatsession.h"
 
40
 
 
41
#include "historyconfig.h"
 
42
 
 
43
bool messageTimestampLessThan(const Kopete::Message &m1, const Kopete::Message &m2)
 
44
{
 
45
        const Kopete::Contact* c1 = (m1.direction() == Kopete::Message::Outbound) ? m1.to().value(0) : m1.from();
 
46
        const Kopete::Contact* c2 = (m2.direction() == Kopete::Message::Outbound) ? m2.to().value(0) : m2.from();
 
47
 
 
48
        if (c1 == c2) // Messages from the same account, keep order as it was saved.
 
49
                return false;
 
50
 
 
51
        return m1.timestamp() < m2.timestamp();
 
52
}
 
53
 
 
54
// -----------------------------------------------------------------------------
 
55
HistoryLogger::HistoryLogger( Kopete::MetaContact *m,  QObject *parent )
 
56
 : QObject(parent)
 
57
{
 
58
        m_saveTimer=0L;
 
59
        m_saveTimerTime=0;
 
60
        m_metaContact=m;
 
61
        m_hideOutgoing=false;
 
62
        m_cachedMonth=-1;
 
63
        m_realMonth=QDate::currentDate().month();
 
64
        m_oldSens=Default;
 
65
        m_filterCaseSensitive=Qt::CaseSensitive;
 
66
        m_filterRegExp=false;
 
67
 
 
68
        //the contact may be destroyed, for example, if the contact changes its metacontact
 
69
        connect(m_metaContact , SIGNAL(destroyed(QObject*)) , this , SLOT(slotMCDeleted()));
 
70
 
 
71
        setPositionToLast();
 
72
}
 
73
 
 
74
 
 
75
HistoryLogger::HistoryLogger( Kopete::Contact *c,  QObject *parent )
 
76
 : QObject(parent)
 
77
{
 
78
        m_saveTimer=0L;
 
79
        m_saveTimerTime=0;
 
80
        m_cachedMonth=-1;
 
81
        m_metaContact=c->metaContact();
 
82
        m_hideOutgoing=false;
 
83
        m_realMonth=QDate::currentDate().month();
 
84
        m_oldSens=Default;
 
85
        m_filterCaseSensitive=Qt::CaseSensitive;
 
86
        m_filterRegExp=false;
 
87
 
 
88
        //the contact may be destroyed, for example, if the contact changes its metacontact
 
89
        connect(m_metaContact , SIGNAL(destroyed(QObject*)) , this , SLOT(slotMCDeleted()));
 
90
 
 
91
        setPositionToLast();
 
92
}
 
93
 
 
94
 
 
95
HistoryLogger::~HistoryLogger()
 
96
{
 
97
        if(m_saveTimer && m_saveTimer->isActive())
 
98
                saveToDisk();
 
99
}
 
100
 
 
101
 
 
102
void HistoryLogger::setPositionToLast()
 
103
{
 
104
        setCurrentMonth(0);
 
105
        m_oldSens = AntiChronological;
 
106
        m_oldMonth=0;
 
107
        m_oldElements.clear();
 
108
}
 
109
 
 
110
 
 
111
void HistoryLogger::setPositionToFirst()
 
112
{
 
113
        setCurrentMonth( getFirstMonth() );
 
114
        m_oldSens = Chronological;
 
115
        m_oldMonth=m_currentMonth;
 
116
        m_oldElements.clear();
 
117
}
 
118
 
 
119
 
 
120
void HistoryLogger::setCurrentMonth(int month)
 
121
{
 
122
        m_currentMonth = month;
 
123
        m_currentElements.clear();
 
124
}
 
125
 
 
126
 
 
127
QDomDocument HistoryLogger::getDocument(const Kopete::Contact *c, unsigned int month , bool canLoad , bool* contain)
 
128
{
 
129
        if(m_realMonth!=QDate::currentDate().month())
 
130
        { //We changed month, our index is not correct anymore, clean memory.
 
131
          // or we will see what i called "the 31 midnight bug"(TM) :-)  -Olivier
 
132
                m_documents.clear();
 
133
                m_cachedMonth=-1;
 
134
                m_currentMonth++; //Not usre it's ok, but should work;
 
135
                m_oldMonth++;     // idem
 
136
                m_realMonth=QDate::currentDate().month();
 
137
        }
 
138
 
 
139
        if(!m_metaContact)
 
140
        { //this may happen if the contact has been moved, and the MC deleted
 
141
                if(c && c->metaContact())
 
142
                        m_metaContact=c->metaContact();
 
143
                else
 
144
                        return QDomDocument();
 
145
        }
 
146
 
 
147
        if(!m_metaContact->contacts().contains(const_cast<Kopete::Contact*>(c)))
 
148
        {
 
149
                if(contain)
 
150
                        *contain=false;
 
151
                return QDomDocument();
 
152
        }
 
153
 
 
154
        QMap<unsigned int , QDomDocument> documents = m_documents[c];
 
155
        if (documents.contains(month))
 
156
                return documents[month];
 
157
 
 
158
 
 
159
        QDomDocument doc =  getDocument(c, QDate::currentDate().addMonths(0-month), canLoad, contain);
 
160
 
 
161
        documents.insert(month, doc);
 
162
        m_documents[c]=documents;
 
163
 
 
164
        return doc;
 
165
 
 
166
}
 
167
 
 
168
QDomDocument HistoryLogger::getDocument(const Kopete::Contact *contact, const QDate date , bool canLoad , bool* contain)
 
169
{
 
170
        Kopete::Contact *c = const_cast<Kopete::Contact*>(contact);
 
171
        if(!m_metaContact)
 
172
        { //this may happen if the contact has been moved, and the MC deleted
 
173
                if(c && c->metaContact())
 
174
                        m_metaContact=c->metaContact();
 
175
                else
 
176
                        return QDomDocument();
 
177
        }
 
178
 
 
179
        if(!m_metaContact->contacts().contains(c))
 
180
        {
 
181
                if(contain)
 
182
                        *contain=false;
 
183
                return QDomDocument();
 
184
        }
 
185
 
 
186
        if(!canLoad)
 
187
        {
 
188
                if(contain)
 
189
                        *contain=false;
 
190
                return QDomDocument();
 
191
        }
 
192
 
 
193
        QString FileName = getFileName(c, date);
 
194
 
 
195
        QDomDocument doc( "Kopete-History" );
 
196
 
 
197
        QFile file( FileName );
 
198
        if ( !file.open( QIODevice::ReadOnly ) )
 
199
        {
 
200
                if(contain)
 
201
                        *contain=false;
 
202
                return doc;
 
203
        }
 
204
        if ( !doc.setContent( &file ) )
 
205
        {
 
206
                file.close();
 
207
                if(contain)
 
208
                        *contain=false;
 
209
                return doc;
 
210
        }
 
211
        file.close();
 
212
 
 
213
        if(contain)
 
214
                *contain=true;
 
215
 
 
216
        return doc;
 
217
}
 
218
 
 
219
 
 
220
void HistoryLogger::appendMessage( const Kopete::Message &msg , const Kopete::Contact *ct )
 
221
{
 
222
        if(!msg.from())
 
223
                return;
 
224
 
 
225
        // If no contact are given: If the manager is availiable, use the manager's
 
226
        // first contact (the channel on irc, or the other contact for others protocols
 
227
        const Kopete::Contact *c = ct;
 
228
        if(!c && msg.manager() )
 
229
        {
 
230
                QList<Kopete::Contact*> mb=msg.manager()->members() ;
 
231
                c = mb.first();
 
232
        }
 
233
        if(!c)  //If the contact is still not initialized, use the message author.
 
234
                c =   msg.direction()==Kopete::Message::Outbound ? msg.to().first() : msg.from()  ;
 
235
 
 
236
 
 
237
        if(!m_metaContact)
 
238
        { //this may happen if the contact has been moved, and the MC deleted
 
239
                if(c && c->metaContact())
 
240
                        m_metaContact=c->metaContact();
 
241
                else
 
242
                        return;
 
243
        }
 
244
 
 
245
 
 
246
        if(!c || !m_metaContact->contacts().contains(const_cast<Kopete::Contact*>(c)) )
 
247
        {
 
248
                /*QPtrList<Kopete::Contact> contacts= m_metaContact->contacts();
 
249
                QPtrListIterator<Kopete::Contact> it( contacts );
 
250
                for( ; it.current(); ++it )
 
251
                {
 
252
                        if( (*it)->protocol()->pluginId() == msg.from()->protocol()->pluginId() )
 
253
                        {
 
254
                                c=*it;
 
255
                                break;
 
256
                        }
 
257
                }*/
 
258
                //if(!c)
 
259
 
 
260
                kWarning(14310) << "No contact found in this metacontact to" <<
 
261
                        " append in the history" << endl;
 
262
                return;
 
263
        }
 
264
 
 
265
        QDate date = msg.timestamp().date();
 
266
                
 
267
        QDomDocument doc=getDocument(c, QDate::currentDate().month() - date.month() - (QDate::currentDate().year() - date.year()) * 12);
 
268
        QDomElement docElem = doc.documentElement();
 
269
 
 
270
        if(docElem.isNull())
 
271
        {
 
272
                docElem= doc.createElement( "kopete-history" );
 
273
                docElem.setAttribute ( "version" , "0.9" );
 
274
                doc.appendChild( docElem );
 
275
                QDomElement headElem = doc.createElement( "head" );
 
276
                docElem.appendChild( headElem );
 
277
                QDomElement dateElem = doc.createElement( "date" );
 
278
                dateElem.setAttribute( "year",  QString::number(date.year()) );
 
279
                dateElem.setAttribute( "month", QString::number(date.month()) );
 
280
                headElem.appendChild(dateElem);
 
281
                QDomElement myselfElem = doc.createElement( "contact" );
 
282
                myselfElem.setAttribute( "type",  "myself" );
 
283
                myselfElem.setAttribute( "contactId", c->account()->myself()->contactId() );
 
284
                headElem.appendChild(myselfElem);
 
285
                QDomElement contactElem = doc.createElement( "contact" );
 
286
                contactElem.setAttribute( "contactId", c->contactId() );
 
287
                headElem.appendChild(contactElem);
 
288
        }
 
289
 
 
290
        QDomElement msgElem = doc.createElement( "msg" );
 
291
        msgElem.setAttribute( "in",  msg.direction()==Kopete::Message::Outbound ? "0" : "1" );
 
292
        msgElem.setAttribute( "from",  msg.from()->contactId() );
 
293
        msgElem.setAttribute( "nick",  msg.from()->property( Kopete::Global::Properties::self()->nickName() ).value().toString() ); //do we have to set this?
 
294
        msgElem.setAttribute( "time", msg.timestamp().toString("d h:m:s") );
 
295
 
 
296
        QDomText msgNode;
 
297
 
 
298
        if ( msg.format() != Qt::PlainText )
 
299
                msgNode = doc.createTextNode( msg.escapedBody() );
 
300
        else
 
301
                msgNode = doc.createTextNode( Qt::escape(msg.plainBody()).replace('\n', "<br />") );
 
302
 
 
303
        docElem.appendChild( msgElem );
 
304
        msgElem.appendChild( msgNode );
 
305
 
 
306
 
 
307
        // I'm temporizing the save.
 
308
        // On hight-traffic channel, saving can take lots of CPU. (because the file is big)
 
309
        // So i wait a time proportional to the time needed to save..
 
310
 
 
311
        const QString filename=getFileName(c, date);
 
312
        if(!m_toSaveFileName.isEmpty() && m_toSaveFileName != filename)
 
313
        { //that mean the contact or the month has changed, save it now.
 
314
                saveToDisk();
 
315
        }
 
316
 
 
317
        m_toSaveFileName=filename;
 
318
        m_toSaveDocument=doc;
 
319
 
 
320
        if(!m_saveTimer)
 
321
        {
 
322
                m_saveTimer=new QTimer(this);
 
323
                connect( m_saveTimer, SIGNAL(timeout()) , this, SLOT(saveToDisk()) );
 
324
        }
 
325
        if(!m_saveTimer->isActive())
 
326
        {
 
327
                m_saveTimer->setSingleShot( true );
 
328
                m_saveTimer->start( m_saveTimerTime );
 
329
        }
 
330
}
 
331
 
 
332
void HistoryLogger::saveToDisk()
 
333
{
 
334
        if(m_saveTimer)
 
335
                m_saveTimer->stop();
 
336
        if(m_toSaveFileName.isEmpty() || m_toSaveDocument.isNull())
 
337
                return;
 
338
 
 
339
        QTime t;
 
340
        t.start(); //mesure the time needed to save.
 
341
 
 
342
        KSaveFile file( m_toSaveFileName );
 
343
        if( file.open() )
 
344
        {
 
345
                QTextStream stream ( &file );
 
346
                //stream.setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non?
 
347
                m_toSaveDocument.save( stream, 1 );
 
348
                file.finalize();
 
349
 
 
350
                m_saveTimerTime=qMin(t.elapsed()*1000, 300000);
 
351
                    //a time 1000 times supperior to the time needed to save.  but with a upper limit of 5 minutes
 
352
                //on a my machine, (2.4Ghz, but old HD) it should take about 10 ms to save the file.
 
353
                // So that would mean save every 10 seconds, which seems to be ok.
 
354
                // But it may take 500 ms if the file to save becomes too big (1Mb).
 
355
                kDebug(14310) << m_toSaveFileName << " saved in " << t.elapsed() << " ms ";
 
356
 
 
357
                m_toSaveFileName.clear();
 
358
                m_toSaveDocument=QDomDocument();
 
359
        }
 
360
        else
 
361
                kError(14310) << "impossible to save the history file " << m_toSaveFileName << endl;
 
362
 
 
363
}
 
364
 
 
365
QList<Kopete::Message> HistoryLogger::readMessages(QDate date)
 
366
{
 
367
        QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility)
 
368
        QList<Kopete::Message> messages;
 
369
        QList<Kopete::Contact*> ct=m_metaContact->contacts();
 
370
 
 
371
        foreach(Kopete::Contact* contact, ct)
 
372
        {
 
373
                QDomDocument doc=getDocument(contact,date, true, 0L);
 
374
                QDomElement docElem = doc.documentElement();
 
375
                QDomNode n = docElem.firstChild();
 
376
 
 
377
                while(!n.isNull())
 
378
                {
 
379
                        QDomElement  msgElem2 = n.toElement();
 
380
                        if( !msgElem2.isNull() && msgElem2.tagName()=="msg")
 
381
                        {
 
382
                                rxTime.indexIn(msgElem2.attribute("time"));
 
383
                                QDateTime dt( QDate(date.year() , date.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt()  ) );
 
384
 
 
385
                                if (dt.date() != date)
 
386
                                {
 
387
                                        n = n.nextSibling();
 
388
                                        continue;
 
389
                                }
 
390
 
 
391
                                Kopete::Message::MessageDirection dir = (msgElem2.attribute("in") == "1") ?
 
392
                                                Kopete::Message::Inbound : Kopete::Message::Outbound;
 
393
 
 
394
                                if(!m_hideOutgoing || dir != Kopete::Message::Outbound)
 
395
                                { //parse only if we don't hide it
 
396
 
 
397
                                        QString f=msgElem2.attribute("from" );
 
398
                                        const Kopete::Contact *from=f.isNull()? 0L : contact->account()->contacts().value( f );
 
399
 
 
400
                                        if(!from)
 
401
                                                from = (dir == Kopete::Message::Inbound) ? contact : contact->account()->myself();
 
402
 
 
403
                                        Kopete::ContactPtrList to;
 
404
                                        to.append( dir==Kopete::Message::Inbound ? contact->account()->myself() : contact );
 
405
 
 
406
                                        Kopete::Message msg(from, to);
 
407
 
 
408
                                        msg.setHtmlBody( QString::fromLatin1("<span title=\"%1\">%2</span>")
 
409
                                                        .arg( dt.toString(Qt::LocalDate), msgElem2.text() ));
 
410
                                        msg.setTimestamp( dt );
 
411
                                        msg.setDirection( dir );
 
412
 
 
413
                                        messages.append(msg);
 
414
                                }
 
415
                        }
 
416
 
 
417
                        n = n.nextSibling();
 
418
                } // end while on messages
 
419
 
 
420
        }
 
421
 
 
422
        //Bubble Sort, can't use qStableSort because we have to compare surrounding items only, mostly
 
423
        //it will be only O(n) sort, because we will only have one contact in metacontact for a day.
 
424
        const int size = messages.size();
 
425
        for (int i = 0; i < size; i++)
 
426
        {
 
427
                bool swap = false;
 
428
                for (int j = 0; j < size - 1; j++)
 
429
                {
 
430
                        if (messageTimestampLessThan(messages.at(j + 1), messages.at(j))) {
 
431
                                messages.swap(j, j + 1);
 
432
                                swap = true;
 
433
                        }
 
434
                }
 
435
 
 
436
                if (!swap)
 
437
                        break;
 
438
        }
 
439
        return messages;
 
440
}
 
441
 
 
442
QList<Kopete::Message> HistoryLogger::readMessages(int lines,
 
443
        const Kopete::Contact *c, Sens sens, bool reverseOrder, bool colorize)
 
444
{
 
445
        //QDate dd =  QDate::currentDate().addMonths(0-m_currentMonth);
 
446
 
 
447
        QList<Kopete::Message> messages;
 
448
 
 
449
        // A regexp useful for this function
 
450
        QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility)
 
451
 
 
452
        if(!m_metaContact)
 
453
        { //this may happen if the contact has been moved, and the MC deleted
 
454
                if(c && c->metaContact())
 
455
                        m_metaContact=c->metaContact();
 
456
                else
 
457
                        return messages;
 
458
        }
 
459
 
 
460
        if(c && !m_metaContact->contacts().contains(const_cast<Kopete::Contact*>(c)) )
 
461
                return messages;
 
462
 
 
463
        if(sens == Default )  //if no sens are selected, just continue in the previous sens
 
464
                sens = m_oldSens ;
 
465
        if( m_oldSens != Default && sens != m_oldSens )
 
466
        { //we changed our sens! so retrieve the old position to fly in the other way
 
467
                m_currentElements= m_oldElements;
 
468
                m_currentMonth=m_oldMonth;
 
469
        }
 
470
        else
 
471
        {
 
472
                m_oldElements=m_currentElements;
 
473
                m_oldMonth=m_currentMonth;
 
474
        }
 
475
        m_oldSens=sens;
 
476
 
 
477
        //getting the color for messages:
 
478
        QColor fgColor = HistoryConfig::history_color();
 
479
 
 
480
        //Hello guest!
 
481
 
 
482
        //there are two algoritms:
 
483
        // - if a contact is given, or the metacontact contain only one contact,  just read the history.
 
484
        // - else, merge the history
 
485
 
 
486
        //the merging algoritm is the following:
 
487
        // we see what contact we have to read first, and we look at the firt date before another contact
 
488
        // has a message with a bigger date.
 
489
 
 
490
        QDateTime timeLimit;
 
491
        const Kopete::Contact *currentContact=c;
 
492
        if(!c && m_metaContact->contacts().count()==1)
 
493
                currentContact=m_metaContact->contacts().first();
 
494
        else if(!c && m_metaContact->contacts().count()== 0)
 
495
        {
 
496
                return messages;
 
497
        }
 
498
 
 
499
        while(messages.count() < lines)
 
500
        {
 
501
                timeLimit=QDateTime();
 
502
                QDomElement msgElem; //here is the message element
 
503
                QDateTime timestamp; //and the timestamp of this message
 
504
 
 
505
                if(!c && m_metaContact->contacts().count()>1)
 
506
                { //we have to merge the differents subcontact history
 
507
                        QList<Kopete::Contact*> ct=m_metaContact->contacts();
 
508
 
 
509
                        foreach(Kopete::Contact *contact, ct)
 
510
                        { //we loop over each contact. we are searching the contact with the next message with the smallest date,
 
511
                          // it will becomes our current contact, and the contact with the mext message with the second smallest
 
512
                          // date, this date will bocomes the limit.
 
513
 
 
514
                                QDomNode n;
 
515
                                if(m_currentElements.contains(contact))
 
516
                                        n=m_currentElements[contact];
 
517
                                else  //there is not yet "next message" register, so we will take the first  (for the current month)
 
518
                                {
 
519
                                        QDomDocument doc=getDocument(contact,m_currentMonth);
 
520
                                        QDomElement docElem = doc.documentElement();
 
521
                                        n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild();
 
522
 
 
523
                                        //i can't drop the root element
 
524
                                        workaround.append(docElem);
 
525
                                }
 
526
                                while(!n.isNull())
 
527
                                {
 
528
                                        QDomElement  msgElem2 = n.toElement();
 
529
                                        if( !msgElem2.isNull() && msgElem2.tagName()=="msg")
 
530
                                        {
 
531
                                                rxTime.indexIn(msgElem2.attribute("time"));
 
532
                                                QDate d=QDate::currentDate().addMonths(0-m_currentMonth);
 
533
                                                QDateTime dt( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt()  ) );
 
534
                                                if(!timestamp.isValid() || ((sens==Chronological )? dt < timestamp : dt > timestamp) )
 
535
                                                {
 
536
                                                        timeLimit=timestamp;
 
537
                                                        timestamp=dt;
 
538
                                                        msgElem=msgElem2;
 
539
                                                        currentContact=contact;
 
540
 
 
541
                                                }
 
542
                                                else if(!timeLimit.isValid() || ((sens==Chronological) ? timeLimit > dt : timeLimit < dt) )
 
543
                                                {
 
544
                                                        timeLimit=dt;
 
545
                                                }
 
546
                                                break;
 
547
                                        }
 
548
                                        n=(sens==Chronological)? n.nextSibling() : n.previousSibling();
 
549
                                }
 
550
                        }
 
551
                }
 
552
                else  //we don't have to merge the history. just take the next item in the contact
 
553
                {
 
554
                        if(m_currentElements.contains(currentContact))
 
555
                                msgElem=m_currentElements[currentContact];
 
556
                        else
 
557
                        {
 
558
                                QDomDocument doc=getDocument(currentContact,m_currentMonth);
 
559
                                QDomElement docElem = doc.documentElement();
 
560
                                QDomNode n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild();
 
561
                                msgElem=QDomElement();
 
562
                                while(!n.isNull()) //continue until we get a msg
 
563
                                {
 
564
                                        msgElem=n.toElement();
 
565
                                        if( !msgElem.isNull() && msgElem.tagName()=="msg")
 
566
                                        {
 
567
                                                m_currentElements[currentContact]=msgElem;
 
568
                                                break;
 
569
                                        }
 
570
                                        n=(sens==Chronological)? n.nextSibling() : n.previousSibling();
 
571
                                }
 
572
 
 
573
                                //i can't drop the root element
 
574
                                workaround.append(docElem);
 
575
                        }
 
576
                }
 
577
 
 
578
 
 
579
                if(msgElem.isNull()) //we don't find ANY messages in any contact for this month. so we change the month
 
580
                {
 
581
                        if(sens==Chronological)
 
582
                        {
 
583
                                if(m_currentMonth <= 0)
 
584
                                        break; //there are no other messages to show. break even if we don't have nb messages
 
585
                                setCurrentMonth(m_currentMonth-1);
 
586
                        }
 
587
                        else
 
588
                        {
 
589
                                if(m_currentMonth >= getFirstMonth(c))
 
590
                                        break; //we don't have any other messages to show
 
591
                                setCurrentMonth(m_currentMonth+1);
 
592
                        }
 
593
                        continue; //begin the loop from the bottom, and find currentContact and timeLimit again
 
594
                }
 
595
 
 
596
                while(
 
597
                        (messages.count() < lines) &&
 
598
                        !msgElem.isNull() &&
 
599
                        (!timestamp.isValid() || !timeLimit.isValid() ||
 
600
                                ((sens==Chronological) ? timestamp <= timeLimit : timestamp >= timeLimit)
 
601
                        ))
 
602
                {
 
603
                        // break this loop, if we have reached the correct number of messages,
 
604
                        // if there are no more messages for this contact, or if we reached
 
605
                        // the timeLimit msgElem is the next message, still not parsed, so
 
606
                        // we parse it now
 
607
 
 
608
                        Kopete::Message::MessageDirection dir = (msgElem.attribute("in") == "1") ?
 
609
                                Kopete::Message::Inbound : Kopete::Message::Outbound;
 
610
 
 
611
                        if(!m_hideOutgoing || dir != Kopete::Message::Outbound)
 
612
                        { //parse only if we don't hide it
 
613
 
 
614
                                if( m_filter.isNull() || ( m_filterRegExp? msgElem.text().contains(QRegExp(m_filter,m_filterCaseSensitive)) : msgElem.text().contains(m_filter,m_filterCaseSensitive) ))
 
615
                                {
 
616
                                        Q_ASSERT(currentContact);
 
617
                                        QString f=msgElem.attribute("from" );
 
618
                                        const Kopete::Contact *from=f.isNull() ? 0L : currentContact->account()->contacts().value(f);
 
619
 
 
620
                                        if( !from )
 
621
                                                from = (dir == Kopete::Message::Inbound) ? currentContact : currentContact->account()->myself();
 
622
 
 
623
                                        Kopete::ContactPtrList to;
 
624
                                        to.append( dir==Kopete::Message::Inbound ? currentContact->account()->myself() : const_cast<Kopete::Contact*>(currentContact) );
 
625
 
 
626
                                        if(!timestamp.isValid())
 
627
                                        {
 
628
                                                //parse timestamp only if it was not already parsed
 
629
                                                rxTime.indexIn(msgElem.attribute("time"));
 
630
                                                QDate d=QDate::currentDate().addMonths(0-m_currentMonth);
 
631
                                                timestamp=QDateTime( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt() , rxTime.cap(5).toUInt() ) );
 
632
                                        }
 
633
 
 
634
                                        Kopete::Message msg(from, to);
 
635
                                        msg.setTimestamp( timestamp );
 
636
                                        msg.setDirection( dir );
 
637
 
 
638
                                        if (colorize)
 
639
                                        {
 
640
                                                msg.setHtmlBody( QString::fromLatin1("<span style=\"color:%1\" title=\"%2\">%3</span>")
 
641
                                                        .arg( fgColor.name(), timestamp.toString(Qt::LocalDate), msgElem.text() ));
 
642
                                                msg.setForegroundColor( fgColor );
 
643
                                                msg.addClass( "history" );
 
644
                                        }
 
645
                                        else
 
646
                                        {
 
647
                                                msg.setHtmlBody( QString::fromLatin1("<span title=\"%1\">%2</span>")
 
648
                                                        .arg( timestamp.toString(Qt::LocalDate), msgElem.text() ));
 
649
                                        }
 
650
 
 
651
                                        if(reverseOrder)
 
652
                                                messages.prepend(msg);
 
653
                                        else
 
654
                                                messages.append(msg);
 
655
                                }
 
656
                        }
 
657
 
 
658
                        //here is the point of workaround. If i drop the root element, this crashes
 
659
                        //get the next message
 
660
                        QDomNode node = ( (sens==Chronological) ? msgElem.nextSibling() :
 
661
                                msgElem.previousSibling() );
 
662
 
 
663
                        msgElem = QDomElement(); //n.toElement();
 
664
                        while (!node.isNull() && msgElem.isNull())
 
665
                        {
 
666
                                msgElem = node.toElement();
 
667
                                if (!msgElem.isNull())
 
668
                                {
 
669
                                        if (msgElem.tagName() == "msg")
 
670
                                        {
 
671
                                                if (!c && (m_metaContact->contacts().count() > 1))
 
672
                                                {
 
673
                                                        // In case of hideoutgoing messages, it is faster to do
 
674
                                                        // this, so we don't parse the date if it is not needed
 
675
                                                        QRegExp rx("(\\d+) (\\d+):(\\d+):(\\d+)");
 
676
                                                        rx.indexIn(msgElem.attribute("time"));
 
677
 
 
678
                                                        QDate d = QDate::currentDate().addMonths(0-m_currentMonth);
 
679
                                                        timestamp = QDateTime(
 
680
                                                                QDate(d.year(), d.month(), rx.cap(1).toUInt()),
 
681
                                                                QTime( rx.cap(2).toUInt(), rx.cap(3).toUInt() ) );
 
682
                                                }
 
683
                                                else
 
684
                                                        timestamp = QDateTime(); //invalid
 
685
                                        }
 
686
                                        else
 
687
                                                msgElem = QDomElement();
 
688
                                }
 
689
 
 
690
                                node = (sens == Chronological) ? node.nextSibling() :
 
691
                                        node.previousSibling();
 
692
                        }
 
693
                        m_currentElements[currentContact]=msgElem;  //this is the next message
 
694
                }
 
695
        }
 
696
 
 
697
        if(messages.count() < lines)
 
698
                m_currentElements.clear(); //current elements are null this can't be allowed
 
699
 
 
700
        return messages;
 
701
}
 
702
 
 
703
QString HistoryLogger::getFileName(const Kopete::Contact* c, QDate date)
 
704
{
 
705
 
 
706
        QString name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
 
707
                QString::fromLatin1( "/" ) +
 
708
                c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
 
709
                QString::fromLatin1( "/" ) +
 
710
        c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
 
711
                date.toString(".yyyyMM");
 
712
 
 
713
        QString filename=KStandardDirs::locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ;
 
714
 
 
715
        //Check if there is a kopete 0.7.x file
 
716
        QFileInfo fi(filename);
 
717
        if(!fi.exists())
 
718
        {
 
719
                name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
 
720
                        QString::fromLatin1( "/" ) +
 
721
                        c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
 
722
                        date.toString(".yyyyMM");
 
723
 
 
724
                QString filename2=KStandardDirs::locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ;
 
725
 
 
726
                QFileInfo fi2(filename2);
 
727
                if(fi2.exists())
 
728
                        return filename2;
 
729
        }
 
730
 
 
731
        return filename;
 
732
 
 
733
}
 
734
 
 
735
unsigned int HistoryLogger::getFirstMonth(const Kopete::Contact *c)
 
736
{
 
737
        if(!c)
 
738
                return getFirstMonth();
 
739
 
 
740
        QRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" );
 
741
 
 
742
        // BEGIN check if there are Kopete 0.7.x
 
743
        QDir d1(KStandardDirs::locateLocal("data",QString("kopete/logs/")+
 
744
                c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-"))
 
745
                ));
 
746
        d1.setFilter( QDir::Files | QDir::NoSymLinks );
 
747
        d1.setSorting( QDir::Name );
 
748
 
 
749
        const QFileInfoList list1 = d1.entryInfoList();
 
750
 
 
751
        foreach(const QFileInfo &fi, list1)
 
752
        {
 
753
                if(fi.fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )))
 
754
                {
 
755
                        rx.indexIn(fi.fileName());
 
756
                        int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt();
 
757
 
 
758
                        if(result < 0)
 
759
                        {
 
760
                                kWarning(14310) << "Kopete only found log file from Kopete 0.7.x made in the future. Check your date!";
 
761
                                break;
 
762
                        }
 
763
                        return result;
 
764
                }
 
765
        }
 
766
        // END of kopete 0.7.x check
 
767
 
 
768
 
 
769
        QDir d(KStandardDirs::locateLocal("data",QString("kopete/logs/")+
 
770
                c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) +
 
771
                QString::fromLatin1( "/" ) +
 
772
                c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )
 
773
                ));
 
774
 
 
775
        d.setFilter( QDir::Files | QDir::NoSymLinks );
 
776
        d.setSorting( QDir::Name );
 
777
 
 
778
        const QFileInfoList list = d.entryInfoList();
 
779
 
 
780
        foreach(const QFileInfo &fi, list)
 
781
        {
 
782
                if(fi.fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )))
 
783
                {
 
784
                        rx.indexIn(fi.fileName());
 
785
                        int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt();
 
786
                        if(result < 0)
 
787
                        {
 
788
                                kWarning(14310) << "Kopete only found log file made in the future. Check your date!";
 
789
                                break;
 
790
                        }
 
791
                        return result;
 
792
                }
 
793
        }
 
794
        return 0;
 
795
}
 
796
 
 
797
unsigned int HistoryLogger::getFirstMonth()
 
798
{
 
799
        if(m_cachedMonth!=-1)
 
800
                return m_cachedMonth;
 
801
 
 
802
        if(!m_metaContact)
 
803
                return 0;
 
804
 
 
805
        int m=0;
 
806
        QList<Kopete::Contact*> contacts=m_metaContact->contacts();
 
807
 
 
808
        foreach(Kopete::Contact* contact, contacts)
 
809
        {
 
810
                int m2=getFirstMonth(contact);
 
811
                if(m2>m) m=m2;
 
812
        }
 
813
        m_cachedMonth=m;
 
814
        return m;
 
815
}
 
816
 
 
817
void HistoryLogger::setHideOutgoing(bool b)
 
818
{
 
819
        m_hideOutgoing = b;
 
820
}
 
821
 
 
822
void HistoryLogger::slotMCDeleted()
 
823
{
 
824
        m_metaContact = 0;
 
825
}
 
826
 
 
827
void HistoryLogger::setFilter(const QString& filter, bool caseSensitive , bool isRegExp)
 
828
{
 
829
        m_filter=filter;
 
830
        m_filterCaseSensitive=caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
 
831
        m_filterRegExp=isRegExp;
 
832
}
 
833
 
 
834
QString HistoryLogger::filter() const
 
835
{
 
836
        return m_filter;
 
837
}
 
838
 
 
839
bool HistoryLogger::filterCaseSensitive() const
 
840
{
 
841
        return (m_filterCaseSensitive == Qt::CaseSensitive);
 
842
}
 
843
 
 
844
bool HistoryLogger::filterRegExp() const
 
845
{
 
846
        return m_filterRegExp;
 
847
}
 
848
 
 
849
QList<int> HistoryLogger::getDaysForMonth(QDate date)
 
850
{
 
851
        QRegExp rxTime("time=\"(\\d+) \\d+:\\d+(:\\d+)?\""); //(with a 0.7.x compatibility)
 
852
 
 
853
        QList<int> dayList;
 
854
 
 
855
        QList<Kopete::Contact*> contacts = m_metaContact->contacts();
 
856
 
 
857
        int lastDay=0;
 
858
        foreach(Kopete::Contact *contact, contacts)
 
859
        {
 
860
//              kDebug() << getFileName(*it, date);
 
861
                QFile file(getFileName(contact, date));
 
862
                if(!file.open(QIODevice::ReadOnly))
 
863
                {
 
864
                        continue;
 
865
                }
 
866
                QTextStream stream(&file);
 
867
                QString fullText = stream.readAll();
 
868
                file.close();
 
869
 
 
870
                int pos = 0;
 
871
                while( (pos = rxTime.indexIn(fullText, pos)) != -1)
 
872
                {
 
873
                        pos += rxTime.matchedLength();
 
874
                        int day=rxTime.capturedTexts()[1].toInt();
 
875
 
 
876
                        if ( day !=lastDay && dayList.indexOf(day) == -1) // avoid duplicates
 
877
                        {
 
878
                                dayList.append(rxTime.capturedTexts()[1].toInt());
 
879
                                lastDay=day;
 
880
                        }
 
881
                }
 
882
        }
 
883
        return dayList;
 
884
}
 
885
 
 
886
#include "historylogger.moc"