1 /*
2     history2logger.cpp
3 
4     Copyright (c) 2012 by Volker Härtel <cyberbeat@gmx.de>
5     Copyright (c) 2003-2004 by Olivier Goffart        <ogoffart@kde.org>
6 
7     Kopete    (c) 2003-2004 by the Kopete developers  <kopete-devel@kde.org>
8 
9     *************************************************************************
10     *                                                                       *
11     * This program is free software; you can redistribute it and/or modify  *
12     * it under the terms of the GNU General Public License as published by  *
13     * the Free Software Foundation; either version 2 of the License, or     *
14     * (at your option) any later version.                                   *
15     *                                                                       *
16     *************************************************************************
17 */
18 
19 #include "history2logger.h"
20 
21 #include <QRegExp>
22 #include <QFile>
23 #include <QDir>
24 #include <QDateTime>
25 #include <QTimer>
26 #include <QTextStream>
27 #include <QList>
28 #include <QDate>
29 #include <QTextDocument>
30 #include <QSqlDatabase>
31 #include <QSqlQuery>
32 #include <QSqlRecord>
33 #include <QHash>
34 
35 #include <kdebug.h>
36 
37 #include <QStandardPaths>
38 
39 #include "kopeteglobal.h"
40 #include "kopetecontact.h"
41 #include "kopeteprotocol.h"
42 #include "kopeteaccount.h"
43 #include "kopetemetacontact.h"
44 #include "kopetemessage.h"
45 #include "kopetechatsession.h"
46 #include "kopetecontactlist.h"
47 
48 #include "history2dialog.h"
49 #include "history2config.h"
50 
51 History2Logger *History2Logger::m_Instance = 0;
52 
History2Logger()53 History2Logger::History2Logger()
54 {
55     QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "kopete_history.db";
56     m_db = QSqlDatabase::addDatabase("QSQLITE", "kopete-history");
57     m_db.setDatabaseName(path);
58     if (!m_db.open()) {
59         return;
60     }
61 
62     // Creates the tables if they do not exist.
63     QSqlQuery query("SELECT name FROM sqlite_master WHERE type='table'", m_db);
64     query.exec();
65 
66     QStringList result;
67     while (query.next()) {
68         result.append(query.value(0).toString());
69     }
70     if (!result.contains("history")) {
71         query.exec(QString("CREATE TABLE history "
72                            "(id INTEGER PRIMARY KEY,"
73                            "protocol TEXT,"
74                            "account TEXT,"
75                            "direction TEXT,"
76                            "me_id TEXT,"
77                            "me_nick TEXT,"
78                            "other_id TEXT,"
79                            "other_nick TEXT,"
80                            "datetime TEXT,"
81                            "message TEXT"
82                            ")"));
83 
84         query.exec(QString("CREATE INDEX datetime ON history (datetime)"));
85         query.exec(QString("CREATE INDEX contact ON history (protocol, account, other_id, datetime)"));
86     }
87 }
88 
~History2Logger()89 History2Logger::~History2Logger()
90 {
91     m_db.close();
92 }
93 
beginTransaction()94 void History2Logger::beginTransaction()
95 {
96     QSqlQuery query("BEGIN TRANSACTION", m_db);
97     query.exec();
98 }
99 
commitTransaction()100 void History2Logger::commitTransaction()
101 {
102     QSqlQuery query("COMMIT TRANSACTION", m_db);
103     query.exec();
104 }
105 
appendMessage(const Kopete::Message & msg,const Kopete::Contact * ct,bool skipDuplicate)106 void History2Logger::appendMessage(const Kopete::Message &msg, const Kopete::Contact *ct, bool skipDuplicate)
107 {
108     if (!msg.from()) {
109         return;
110     }
111     if (!msg.timestamp().isValid()) {
112         return;
113     }
114     // If no contact are given: If the manager is availiable, use the manager's
115     // first contact (the channel on irc, or the other contact for others protocols
116     const Kopete::Contact *c = ct;
117     if (!c && msg.manager()) {
118         QList<Kopete::Contact *> mb = msg.manager()->members();
119         c = mb.first();
120     }
121     if (!c) { //If the contact is still not initialized, use the message author.
122         c = msg.direction() == Kopete::Message::Outbound ? msg.to().first() : msg.from();
123     }
124 
125     const Kopete::Contact *me;
126     const Kopete::Contact *other;
127     if (msg.direction() == msg.Inbound) {
128         me = msg.to().first();
129         other = msg.from();
130     } else if (msg.direction() == msg.Outbound) {
131         me = msg.from();
132         other = msg.to().first();
133     } else {
134         return;
135     }
136 
137     QSqlQuery query(m_db);
138 
139     if (skipDuplicate) {
140         if (messageExists(msg, c)) {
141             return;
142         }
143     }
144 
145     query.prepare("INSERT INTO history (direction, protocol, account, me_id, me_nick, other_id, other_nick, datetime, message) "
146                   "VALUES (:direction, :protocol, :account, :me_id, :me_nick, :other_id, :other_nick, :datetime, :message)");
147     query.bindValue(":direction", msg.direction());
148     query.bindValue(":me_id", me->contactId());
149     query.bindValue(":me_nick", me->displayName());
150     query.bindValue(":other_id", other->contactId());
151     query.bindValue(":other_nick", other->displayName());
152     query.bindValue(":datetime", msg.timestamp());
153     query.bindValue(":protocol", ct->protocol()->pluginId());
154     query.bindValue(":account", ct->account()->accountId());
155     query.bindValue(":message", msg.plainBody());
156     query.exec();
157 }
158 
messageExists(const Kopete::Message & msg,const Kopete::Contact * ct)159 bool History2Logger::messageExists(const Kopete::Message &msg, const Kopete::Contact *ct)
160 {
161     if (!msg.from()) {
162         return true;
163     }
164 
165     // If no contact are given: If the manager is availiable, use the manager's
166     // first contact (the channel on irc, or the other contact for others protocols
167     const Kopete::Contact *c = ct;
168     if (!c && msg.manager()) {
169         QList<Kopete::Contact *> mb = msg.manager()->members();
170         c = mb.first();
171     }
172     if (!c) { //If the contact is still not initialized, use the message author.
173         c = msg.direction() == Kopete::Message::Outbound ? msg.to().first() : msg.from();
174     }
175 
176     const Kopete::Contact *me;
177     const Kopete::Contact *other;
178     if (msg.direction() == msg.Inbound) {
179         me = msg.to().first();
180         other = msg.from();
181     } else if (msg.direction() == msg.Outbound) {
182         me = msg.from();
183         other = msg.to().first();
184     } else {
185         return true;
186     }
187 
188     QSqlQuery query(m_db);
189 
190     query.prepare(
191         "SELECT 1 FROM history WHERE direction = :direction AND protocol = :protocol AND account= :account AND me_id = :me_id AND other_id = :other_id AND datetime = :datetime AND message = :message");
192 
193     query.bindValue(":direction", msg.direction());
194     query.bindValue(":me_id", me->contactId());
195     query.bindValue(":other_id", other->contactId());
196     query.bindValue(":datetime", msg.timestamp());
197     query.bindValue(":protocol", ct->protocol()->pluginId());
198     query.bindValue(":account", ct->account()->accountId());
199     query.bindValue(":message", msg.plainBody());
200     query.exec();
201     if (query.next()) {
202         return true;
203     }
204     return false;
205 }
206 
readMessages(QDate date,const Kopete::MetaContact * c)207 QList<Kopete::Message> History2Logger::readMessages(QDate date, const Kopete::MetaContact *c)
208 {
209     QList<Kopete::Message> messages;
210     Kopete::Account *account;
211     Kopete::Contact *from;
212     Kopete::Contact *to;
213     Kopete::Contact *other;
214     Kopete::Contact *me;
215 
216     QStringList list;
217     foreach (Kopete::Contact *ct, c->contacts()) {
218         list.append("(other_id = '"+ct->contactId()+"' AND protocol = '"+ct->account()->protocol()->pluginId()+"' AND account = '"+ct->account()->accountId()+"')");
219     }
220     QSqlQuery query("SELECT * FROM history WHERE ("+list.join(" OR ") + ") AND datetime LIKE '"+date.toString(Qt::ISODate)+"%' ORDER BY datetime", m_db);
221     query.exec();
222     while (query.next()) {
223         QSqlRecord r = query.record();
224         other = 0;
225         foreach (Kopete::Contact *ct, c->contacts()) {
226             if (ct->contactId() == r.value("other_id").toString()) {
227                 other = ct;
228             }
229         }
230         if (!other) {
231             continue;
232         }
233         account = other->account();
234         me = account->myself();
235         if (r.value("direction").toString() == "0") {
236             from = other;
237             to = me;
238         } else {
239             from = me;
240             to = other;
241         }
242         Kopete::Message m(from, to);
243         m.setDirection(r.value("direction").toString() == "0" ? Kopete::Message::Inbound : Kopete::Message::Outbound);
244         QString message(r.value("message").toString());
245 
246         m.setHtmlBody(message);
247 
248         m.setTimestamp(r.value("datetime").toDateTime());
249         messages.append(m);
250     }
251     return messages;
252 }
253 
readMessages(int lines,int offset,const Kopete::MetaContact * c,bool reverseOrder)254 QList<Kopete::Message> History2Logger::readMessages(int lines, int offset, const Kopete::MetaContact *c, bool reverseOrder)
255 {
256     QList<Kopete::Message> messages;
257     Kopete::Account *account;
258     Kopete::Contact *from;
259     Kopete::Contact *to;
260     Kopete::Contact *other;
261     Kopete::Contact *me;
262 
263     QStringList list;
264     foreach (Kopete::Contact *ct, c->contacts()) {
265         list.append("(other_id = '"+ct->contactId()+"' AND protocol = '"+ct->account()->protocol()->pluginId()+"' AND account = '"+ct->account()->accountId()+"')");
266     }
267     QString queryString = "SELECT * FROM history WHERE ("+list.join(" OR ") + ") ORDER BY datetime";
268     if (reverseOrder) {
269         queryString += " DESC";
270     }
271     queryString += QString(" LIMIT %1 OFFSET %2").arg(lines).arg(offset);
272 
273     QSqlQuery query(queryString, m_db);
274     query.exec();
275     while (query.next()) {
276         QSqlRecord r = query.record();
277         other = 0;
278         foreach (Kopete::Contact *ct, c->contacts()) {
279             if (ct->contactId() == r.value("other_id").toString()) {
280                 other = ct;
281             }
282         }
283         if (!other) {
284             continue;
285         }
286         account = other->account();
287         me = account->myself();
288         if (r.value("direction").toString() == "0") {
289             from = other;
290             to = me;
291         } else {
292             from = me;
293             to = other;
294         }
295         Kopete::Message m(from, to);
296         m.setDirection(r.value("direction").toString() == "0" ? Kopete::Message::Inbound : Kopete::Message::Outbound);
297         QString message(r.value("message").toString());
298         m.setHtmlBody(message);
299         m.setTimestamp(r.value("datetime").toDateTime());
300         if (reverseOrder) {
301             messages.prepend(m);
302         } else {
303             messages.append(m);
304         }
305     }
306     return messages;
307 }
308 
getDays(const Kopete::MetaContact * c,QString search)309 QList<QDate> History2Logger::getDays(const Kopete::MetaContact *c, QString search)
310 {
311     QList<QDate> dayList;
312     QString queryString;
313     QString searchQuery = "";
314     if (!search.isEmpty()) {
315         searchQuery = " AND message LIKE '%"+search+"%'";
316     }
317 
318     QStringList list;
319     foreach (Kopete::Contact *ct, c->contacts()) {
320         list.append("(other_id = '"+ct->contactId()+"' AND protocol = '"+ct->account()->protocol()->pluginId()+"' AND account = '"+ct->account()->accountId()+"')");
321     }
322     queryString = "SELECT DISTINCT strftime('%Y-%m-%d',datetime) AS day FROM history WHERE ("+list.join(" OR ") + ")  "+searchQuery+" ORDER BY datetime";
323 
324     QSqlQuery query(queryString, m_db);
325     query.exec();
326     while (query.next()) {
327         dayList.append(query.value(0).toDate());
328     }
329 
330     return dayList;
331 }
332 
getDays(QString search)333 QList<DMPair> History2Logger::getDays(QString search)
334 {
335     QList<DMPair> dayList;
336     QHash<QString, QHash< Kopete::MetaContact *, int> * > hash;
337     QList<QString> dates;
338     QString queryString;
339     QString searchQuery = "";
340     if (!search.isEmpty()) {
341         searchQuery = "WHERE message LIKE '%"+search+"%'";
342     }
343     queryString = "SELECT DISTINCT strftime('%Y-%m-%d',datetime) AS day, protocol, account, other_id FROM history "+searchQuery+" ORDER BY datetime";
344 
345     QSqlQuery query(queryString, m_db);
346     query.exec();
347     while (query.next()) {
348         Kopete::Contact *c = Kopete::ContactList::self()->findContact(query.value(1).toString(), query.value(2).toString(), query.value(3).toString());
349         if (!c) {
350             continue;
351         }
352         QString date = query.value(0).toString();
353         if (!hash.contains(date)) {
354             hash.insert(date, new QHash< Kopete::MetaContact *, int>());
355             dates.append(date);
356         }
357         QHash< Kopete::MetaContact *, int> *h = hash.value(date);
358         h->insert(c->metaContact(), 1);
359     }
360     foreach (QString date, dates) {
361         foreach (Kopete::MetaContact *mc, hash.value(date)->keys()) {
362             DMPair pair(QDate::fromString(date, Qt::ISODate), mc);
363             dayList.append(pair);
364         }
365     }
366 
367     return dayList;
368 }
369