Statistics
| Branch: | Tag: | Revision:

root / plugins / sql_history / storage / history-sql-storage.cpp @ a5c5b6a2

History | View | Annotate | Download (31.1 kB)

1
/*
2
 * %kadu copyright begin%
3
 * Copyright 2009, 2010, 2010, 2011 Piotr Galiszewski (piotr.galiszewski@kadu.im)
4
 * Copyright 2009, 2009, 2009, 2009, 2010 Wojciech Treter (juzefwt@gmail.com)
5
 * Copyright 2009 Michał Podsiadlik (michal@kadu.net)
6
 * Copyright 2009 Bartłomiej Zimoń (uzi18@o2.pl)
7
 * Copyright 2009, 2010, 2011 Rafał Malinowski (rafal.przemyslaw.malinowski@gmail.com)
8
 * Copyright 2010, 2011 Bartosz Brachaczek (b.brachaczek@gmail.com)
9
 * %kadu copyright end%
10
 *
11
 * This program is free software; you can redistribute it and/or
12
 * modify it under the terms of the GNU General Public License as
13
 * published by the Free Software Foundation; either version 2 of
14
 * the License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
#include <QtCore/QDir>
26
#include <QtCore/QMutexLocker>
27
#include <QtCore/QThread>
28
#include <QtCore/QtConcurrentRun>
29
#include <QtGui/QTextDocument>
30
#include <QtSql/QSqlError>
31
#include <QtSql/QSqlRecord>
32
33
#include "accounts/account-manager.h"
34
#include "accounts/account.h"
35
#include "buddies/buddy-manager.h"
36
#include "chat/chat-details-aggregate.h"
37
#include "chat/chat-details.h"
38
#include "chat/chat-manager.h"
39
#include "configuration/configuration-file.h"
40
#include "contacts/contact-manager.h"
41
#include "contacts/contact-set.h"
42
#include "core/core.h"
43
#include "gui/widgets/chat-widget.h"
44
#include "gui/windows/message-dialog.h"
45
#include "gui/windows/progress-window2.h"
46
#include "message/formatted-message.h"
47
#include "message/message.h"
48
#include "misc/misc.h"
49
#include "misc/path-conversion.h"
50
#include "status/status-type-manager.h"
51
#include "status/status-type-data.h"
52
#include "status/status-type-manager.h"
53
#include "talkable/talkable.h"
54
#include "debug.h"
55
56
#include "plugins/history/history.h"
57
#include "plugins/history/model/dates-model-item.h"
58
#include "plugins/history/search/history-search-parameters.h"
59
#include "plugins/history/timed-status.h"
60
61
#include "storage/sql-initializer.h"
62
63
#include "history-sql-storage.h"
64
65
#define DATE_TITLE_LENGTH 120
66
67
HistorySqlStorage::HistorySqlStorage(QObject *parent) :
68
                HistoryStorage(parent), ImportProgressWindow(0), DatabaseMutex(QMutex::NonRecursive)
69
{
70
        kdebugf();
71
72
        if (!QSqlDatabase::isDriverAvailable("QSQLITE"))
73
        {
74
                MessageDialog::show(KaduIcon("dialog-warning"), tr("Kadu"),
75
                                tr("It seems your Qt library does not provide support for selected database. "
76
                                   "Please install Qt with %1 plugin.").arg("QSQLITE"));
77
                return;
78
        }
79
80
        qRegisterMetaType<QSqlError>("QSqlError");
81
82
        InitializerThread = new QThread();
83
84
        // this object cannot have parent as it will be moved to a new thread
85
        SqlInitializer *initializer = new SqlInitializer();
86
        initializer->moveToThread(InitializerThread);
87
88
        connect(InitializerThread, SIGNAL(started()), initializer, SLOT(initialize()));
89
        connect(initializer, SIGNAL(initialized()), InitializerThread, SLOT(quit()));
90
        connect(initializer, SIGNAL(databaseReady(bool)), this, SLOT(databaseReady(bool)));
91
        connect(initializer, SIGNAL(importStarted()), this, SLOT(importStarted()));
92
        connect(initializer, SIGNAL(importFinished()), this, SLOT(importFinished()));
93
        connect(initializer, SIGNAL(databaseOpenFailed(QSqlError)), this, SLOT(databaseOpenFailed(QSqlError)));
94
95
        InitializerThread->start();
96
97
        History::instance()->registerStorage(this);
98
}
99
100
HistorySqlStorage::~HistorySqlStorage()
101
{
102
        kdebugf();
103
104
        if (InitializerThread && InitializerThread->isRunning())
105
        {
106
                InitializerThread->terminate();
107
                InitializerThread->wait(2000);
108
        }
109
110
        delete InitializerThread;
111
        InitializerThread = 0;
112
113
        if (Database.isOpen())
114
                Database.commit();
115
}
116
117
void HistorySqlStorage::databaseReady(bool ok)
118
{
119
        if (ok)
120
                Database = QSqlDatabase::database("kadu-history", true);
121
122
        if (!Database.isOpen())
123
        {
124
                databaseOpenFailed(Database.lastError());
125
                History::instance()->unregisterStorage(this);
126
                return;
127
        }
128
129
        Database.transaction();
130
        initQueries();
131
}
132
133
void HistorySqlStorage::importStarted()
134
{
135
        ImportProgressWindow = new ProgressWindow2(
136
                    tr("Optimizing history database. This can take several minutes.\n"
137
                       "Please do not close Kadu until optimalization is complete.")
138
        );
139
        ImportProgressWindow->show();
140
}
141
142
void HistorySqlStorage::importFinished()
143
{
144
        if (ImportProgressWindow)
145
        {
146
                ImportProgressWindow->setText(tr("Optimalization complete. You can now close this window."));
147
                ImportProgressWindow->enableClosing();
148
        }
149
}
150
151
void HistorySqlStorage::databaseOpenFailed (const QSqlError &error)
152
{
153
        if (ImportProgressWindow)
154
        {
155
                ImportProgressWindow->setText(tr("Optimalization failed. Error message:\n%1").arg(error.text()));
156
                ImportProgressWindow->enableClosing();
157
        }
158
        else
159
                MessageDialog::show(KaduIcon("dialog-warning"), tr("Kadu"), error.text());
160
}
161
162
bool HistorySqlStorage::isDatabaseReady(bool wait)
163
{
164
        if (InitializerThread && InitializerThread->isRunning())
165
        {
166
                if (wait)
167
                        InitializerThread->wait();
168
                else
169
                        return false;
170
        }
171
172
        return Database.isOpen();
173
}
174
175
void HistorySqlStorage::initQueries()
176
{
177
        AppendMessageQuery = QSqlQuery(Database);
178
        AppendMessageQuery.prepare("INSERT INTO kadu_messages (chat_id, contact_id, send_time, receive_time, date_id, is_outgoing, content_id) VALUES "
179
                        "(:chat_id, :contact_id, :send_time, :receive_time, :date_id, :is_outgoing, :content_id)");
180
181
        AppendStatusQuery = QSqlQuery(Database);
182
        AppendStatusQuery.prepare("INSERT INTO kadu_statuses (contact, status, set_time, description) VALUES "
183
                        "(:contact, :status, :set_time, :description)");
184
185
        AppendSmsQuery = QSqlQuery(Database);
186
        AppendSmsQuery.prepare("INSERT INTO kadu_sms (receipient, send_time, content) VALUES "
187
                        "(:receipient, :send_time, :content)");
188
}
189
190
QString HistorySqlStorage::chatWhere(const Chat &chat, const QString &chatPrefix)
191
{
192
        if (!chat)
193
                return QLatin1String("false");
194
195
        ChatDetails *details = chat.details();
196
        if (!details)
197
                return QLatin1String("false");
198
199
        ChatDetailsAggregate *aggregate = qobject_cast<ChatDetailsAggregate *>(details);
200
        if (!aggregate)
201
                return QString("%1uuid = '%2'").arg(chatPrefix).arg(chat.uuid().toString());
202
203
        QStringList uuids;
204
        foreach (const Chat &aggregatedChat, aggregate->chats())
205
                uuids.append(QString("'%1'").arg(aggregatedChat.uuid().toString()));
206
207
        return QString("%1uuid IN (%2)").arg(chatPrefix).arg(uuids.join(QLatin1String(", ")));
208
}
209
210
QString HistorySqlStorage::buddyContactsWhere(const Buddy &buddy, const QString &fieldName)
211
{
212
        if (!buddy || buddy.contacts().isEmpty())
213
                return  QLatin1String("false");
214
215
        QStringList uuids;
216
        foreach (const Contact &contact, buddy.contacts())
217
                uuids.append(QString("'%1'").arg(contact.uuid().toString()));
218
219
        return QString("(%1) IN (%2)").arg(fieldName).arg(uuids.join(QLatin1String(", ")));
220
}
221
222
void HistorySqlStorage::sync()
223
{
224
        if (!isDatabaseReady(false))
225
                return; // nothing to sync yet
226
227
        QMutexLocker locker(&DatabaseMutex);
228
229
        Database.commit();
230
        Database.transaction();
231
}
232
233
void HistorySqlStorage::messageReceived(const Message &message)
234
{
235
        appendMessage(message);
236
}
237
238
void HistorySqlStorage::messageSent(const Message &message)
239
{
240
        appendMessage(message);
241
}
242
243
int HistorySqlStorage::findOrCreateChat(const Chat &chat)
244
{
245
        if (ChatMap.contains(chat))
246
                return ChatMap.value(chat);
247
248
        QSqlQuery query(Database);
249
        QString queryString = "SELECT id FROM kadu_chats WHERE uuid=:uuid";
250
251
        query.prepare(queryString);
252
        query.bindValue(":uuid", chat.uuid().toString());
253
254
        int chatId = -1;
255
256
        executeQuery(query);
257
258
        if (query.next())
259
        {
260
                chatId = query.value(0).toInt();
261
                Q_ASSERT(!query.next());
262
        }
263
        else
264
        {
265
                QSqlQuery query(Database);
266
                QString queryString = "INSERT INTO kadu_chats (uuid) VALUES (:uuid)";
267
268
                query.prepare(queryString);
269
                query.bindValue(":uuid", chat.uuid().toString());
270
271
                executeQuery(query);
272
                chatId = query.lastInsertId().toInt();
273
        }
274
275
        ChatMap.insert(chat, chatId);
276
277
        return chatId;
278
}
279
280
int HistorySqlStorage::findOrCreateContact(const Contact &contact)
281
{
282
        if (ContactMap.contains(contact))
283
                return ContactMap.value(contact);
284
285
        QSqlQuery query(Database);
286
        QString queryString = "SELECT id FROM kadu_contacts WHERE uuid=:uuid";
287
288
        query.prepare(queryString);
289
        query.bindValue(":uuid", contact.uuid().toString());
290
291
        int contactId = -1;
292
293
        executeQuery(query);
294
295
        if (query.next())
296
        {
297
                contactId = query.value(0).toInt();
298
                Q_ASSERT(!query.next());
299
        }
300
        else
301
        {
302
                QSqlQuery query(Database);
303
                QString queryString = "INSERT INTO kadu_contacts (uuid) VALUES (:uuid)";
304
305
                query.prepare(queryString);
306
                query.bindValue(":uuid", contact.uuid().toString());
307
308
                executeQuery(query);
309
                contactId = query.lastInsertId().toInt();
310
        }
311
312
        ContactMap.insert(contact, contactId);
313
314
        return contactId;
315
}
316
317
int HistorySqlStorage::findOrCreateDate(const QDate &date)
318
{
319
        QString stringDate = date.toString("yyyyMMdd");
320
        if (DateMap.contains(stringDate))
321
                return DateMap.value(stringDate);
322
323
        QSqlQuery query(Database);
324
        QString queryString = "SELECT id FROM kadu_dates WHERE date=:date";
325
326
        query.prepare(queryString);
327
        query.bindValue(":date", stringDate);
328
329
        int dateId = -1;
330
331
        executeQuery(query);
332
333
        if (query.next())
334
        {
335
                dateId = query.value(0).toInt();
336
                Q_ASSERT(!query.next());
337
        }
338
        else
339
        {
340
                QSqlQuery query(Database);
341
                QString queryString = "INSERT INTO kadu_dates (date) VALUES (:date)";
342
343
                query.prepare(queryString);
344
                query.bindValue(":date", stringDate);
345
346
                executeQuery(query);
347
                dateId = query.lastInsertId().toInt();
348
        }
349
350
        DateMap.insert(stringDate, dateId);
351
352
        return dateId;
353
}
354
355
int HistorySqlStorage::saveMessageContent(const Message& message)
356
{
357
        QSqlQuery saveMessageQuery = QSqlQuery(Database);
358
        saveMessageQuery.prepare("INSERT INTO kadu_message_contents (content) VALUES (:content)");
359
360
        saveMessageQuery.bindValue(":content", message.content());
361
362
        executeQuery(saveMessageQuery);
363
        int contentId = saveMessageQuery.lastInsertId().toInt();
364
365
        saveMessageQuery.finish();
366
367
        return contentId;
368
}
369
370
void HistorySqlStorage::appendMessage(const Message &message)
371
{
372
        kdebugf();
373
374
        if (!isDatabaseReady(true))
375
                return;
376
377
        QMutexLocker locker(&DatabaseMutex);
378
379
        int outgoing = (message.type() == MessageTypeSent)
380
                        ? 1
381
                        : 0;
382
383
        AppendMessageQuery.bindValue(":chat_id", findOrCreateChat(message.messageChat()));
384
        AppendMessageQuery.bindValue(":contact_id", findOrCreateContact(message.messageSender()));
385
        AppendMessageQuery.bindValue(":send_time", message.sendDate());
386
        AppendMessageQuery.bindValue(":receive_time", message.receiveDate());
387
        AppendMessageQuery.bindValue(":date_id", findOrCreateDate(message.receiveDate().date()));
388
        AppendMessageQuery.bindValue(":is_outgoing", outgoing);
389
        AppendMessageQuery.bindValue(":content_id", saveMessageContent(message));
390
391
        executeQuery(AppendMessageQuery);
392
393
        AppendMessageQuery.finish();
394
395
        kdebugf2();
396
}
397
398
void HistorySqlStorage::appendStatus(const Contact &contact, const Status &status, const QDateTime &time)
399
{
400
        kdebugf();
401
402
        if (!isDatabaseReady(true))
403
                return;
404
405
        QMutexLocker locker(&DatabaseMutex);
406
407
        StatusTypeData statusTypeData = StatusTypeManager::instance()->statusTypeData(status.type());
408
409
        AppendStatusQuery.bindValue(":contact", contact.uuid().toString());
410
        AppendStatusQuery.bindValue(":status", statusTypeData.name());
411
        AppendStatusQuery.bindValue(":set_time", time);
412
        AppendStatusQuery.bindValue(":description", status.description());
413
414
        executeQuery(AppendStatusQuery);
415
416
        AppendStatusQuery.finish();
417
418
        kdebugf2();
419
}
420
421
void HistorySqlStorage::appendSms(const QString &recipient, const QString &content, const QDateTime &time)
422
{
423
        kdebugf();
424
425
        if (!isDatabaseReady(true))
426
                return;
427
428
        QMutexLocker locker(&DatabaseMutex);
429
430
        AppendSmsQuery.bindValue(":contact", recipient);
431
        AppendSmsQuery.bindValue(":send_time", time);
432
        AppendSmsQuery.bindValue(":content", content);
433
434
        executeQuery(AppendSmsQuery);
435
436
        AppendSmsQuery.finish();
437
438
        kdebugf2();
439
}
440
441
void HistorySqlStorage::clearChatHistory(const Chat &chat, const QDate &date)
442
{
443
        if (!isDatabaseReady(true))
444
                return;
445
446
        QMutexLocker locker(&DatabaseMutex);
447
448
        QSqlQuery query(Database);
449
        QString queryString = "DELETE FROM kadu_messages WHERE chat_id IN (SELECT id FROM kadu_chats chat WHERE " + chatWhere(chat) + ")";
450
        if (!date.isNull())
451
                queryString += " AND date_id IN (SELECT id FROM kadu_dates WHERE date = :date)";
452
453
        query.prepare(queryString);
454
455
        if (!date.isNull())
456
                query.bindValue(":date", date.toString("yyyyMMdd"));
457
458
        executeQuery(query);
459
460
        QString removeChatsQueryString = "DELETE FROM kadu_chats WHERE " + chatWhere(chat, "") +
461
                " AND 0 = (SELECT count(*) FROM kadu_messages WHERE chat_id = kadu_chats.id)";
462
463
        QSqlQuery removeChatsQuery(Database);
464
465
        removeChatsQuery.prepare(removeChatsQueryString);
466
467
        executeQuery(removeChatsQuery);
468
}
469
470
void HistorySqlStorage::clearStatusHistory(const Buddy &buddy, const QDate &date)
471
{
472
        if (!isDatabaseReady(true))
473
                return;
474
475
        QMutexLocker locker(&DatabaseMutex);
476
477
        QSqlQuery query(Database);
478
        QString queryString = "DELETE FROM kadu_statuses WHERE " + buddyContactsWhere(buddy, "contact");
479
        if (!date.isNull())
480
                queryString += " AND substr(set_time,0,11) = :date";
481
482
        query.prepare(queryString);
483
484
        if (!date.isNull())
485
                query.bindValue(":date", date.toString(Qt::ISODate));
486
487
        executeQuery(query);
488
}
489
490
void HistorySqlStorage::clearSmsHistory(const QString &recipient, const QDate &date)
491
{
492
        if (!isDatabaseReady(true))
493
                return;
494
495
        QMutexLocker locker(&DatabaseMutex);
496
497
        QSqlQuery query(Database);
498
        QString queryString = "DELETE FROM kadu_sms WHERE receipient = :receipient";
499
        if (!date.isNull())
500
                queryString += " AND substr(send_time,0,11) = :date";
501
502
        query.prepare(queryString);
503
504
        query.bindValue(":receipient", recipient);
505
        if (!date.isNull())
506
                query.bindValue(":date", date.toString(Qt::ISODate));
507
508
        executeQuery(query);
509
}
510
511
void HistorySqlStorage::deleteHistory(const Buddy &buddy)
512
{
513
        if (!isDatabaseReady(true))
514
                return;
515
516
        QMutexLocker locker(&DatabaseMutex);
517
518
        QSqlQuery query(Database);
519
520
        foreach (const Contact &contact, buddy.contacts())
521
        {
522
                Chat chat = ChatManager::instance()->findChat(ContactSet(contact), false);
523
                if (chat)
524
                {
525
                        QString queryString = "DELETE FROM kadu_messages LEFT JOIN kadu_chats chat ON (kadu_messages.chat_id=chat.id) WHERE " + chatWhere(chat);
526
                        query.prepare(queryString);
527
                        executeQuery(query);
528
                }
529
        }
530
531
        QString queryString = "DELETE FROM kadu_statuses WHERE " + buddyContactsWhere(buddy, "contact");
532
        query.prepare(queryString);
533
        executeQuery(query);
534
}
535
536
QVector<Chat> HistorySqlStorage::chats(const HistorySearchParameters &search)
537
{
538
        kdebugf();
539
540
        if (!isDatabaseReady(false))
541
                return QVector<Chat>();
542
543
        QMutexLocker locker(&DatabaseMutex);
544
545
        QString joins = !search.query().isEmpty() || search.fromDate().isValid() || search.toDate().isValid()
546
                ? "LEFT JOIN kadu_messages km ON (kadu_chats.id=km.chat_id) LEFT JOIN kadu_dates kd ON (kd.id=km.date_id) LEFT JOIN kadu_message_contents kmc ON (kmc.id=km.content_id) "
547
                : "";
548
549
        QSqlQuery query(Database);
550
        QString queryString = "SELECT uuid FROM kadu_chats " + joins + "WHERE 1";
551
552
        if (!search.query().isEmpty())
553
                queryString += " AND content LIKE :content";
554
        if (search.fromDate().isValid())
555
                queryString += " AND date >= :fromDate";
556
        if (search.toDate().isValid())
557
                queryString += " AND date <= :toDate";
558
559
        query.prepare(queryString);
560
561
        if (!search.query().isEmpty())
562
                query.bindValue(":content", QString('%' + search.query() + '%'));
563
        if (search.fromDate().isValid())
564
                query.bindValue(":fromDate", search.fromDate().toString("yyyyMMdd"));
565
        if (search.toDate().isValid())
566
                query.bindValue(":toDate", search.toDate().toString("yyyyMMdd"));
567
568
        QVector<Chat> chats;
569
570
        executeQuery(query);
571
572
        while (query.next())
573
        {
574
                Chat chat = ChatManager::instance()->byUuid(query.value(0).toString());
575
                if (chat)
576
                        chats.append(chat);
577
        }
578
579
        return chats;
580
}
581
582
QVector<DatesModelItem> HistorySqlStorage::chatDates(const Chat &chat, const HistorySearchParameters &search)
583
{
584
        kdebugf();
585
586
        if (!chat)
587
                return QVector<DatesModelItem>();
588
589
        if (!isDatabaseReady(false))
590
                return QVector<DatesModelItem>();
591
592
        QMutexLocker locker(&DatabaseMutex);
593
594
        QSqlQuery query(Database);
595
        QString queryString = "SELECT count(1), date, content FROM";
596
        queryString += " (SELECT km.rowid, date, date_id, content FROM kadu_messages km "
597
                "LEFT JOIN kadu_message_contents kmc ON (km.content_id=kmc.id) "
598
                "LEFT JOIN kadu_dates d ON (km.date_id=d.id) "
599
                "LEFT JOIN kadu_chats chat ON (km.chat_id=chat.id) WHERE " + chatWhere(chat);
600
        if (!search.query().isEmpty())
601
          queryString += " AND kmc.content LIKE :content";
602
        if (search.fromDate().isValid())
603
          queryString += " AND date >= :fromDate";
604
        if (search.toDate().isValid())
605
          queryString += " AND date <= :toDate";
606
        queryString += " ORDER BY date_id DESC, km.rowid DESC )";
607
        queryString += " GROUP BY date_id";
608
        queryString += " ORDER BY date ASC, rowid ASC";
609
610
        query.prepare(queryString);
611
612
        if (!search.query().isEmpty())
613
                query.bindValue(":content", QString('%' + search.query() + '%'));
614
        if (search.fromDate().isValid())
615
                query.bindValue(":fromDate", search.fromDate().toString("yyyyMMdd"));
616
        if (search.toDate().isValid())
617
                query.bindValue(":toDate", search.toDate().toString("yyyyMMdd"));
618
619
        QVector<DatesModelItem> dates;
620
        executeQuery(query);
621
622
        int count;
623
        QString message;
624
        QDate date;
625
        while (query.next())
626
        {
627
                count = query.value(0).toInt();
628
629
                QString dateString = query.value(1).toString();
630
                date = QDate::fromString(dateString, "yyyyMMdd");
631
                if (!date.isValid())
632
                        continue;
633
634
                message = query.value(2).toString();
635
                if (message.isEmpty())
636
                        continue;
637
638
                // TODO: this should be done in different place
639
                QTextDocument document;
640
                document.setHtml(message);
641
                FormattedMessage formatted = FormattedMessage::parse(&document);
642
                QString title = formatted.toPlain();
643
644
                if (title.length() > DATE_TITLE_LENGTH)
645
                {
646
                        title.truncate(DATE_TITLE_LENGTH);
647
                        title += " ...";
648
                }
649
650
                dates.append(DatesModelItem(date, title, count));
651
        }
652
653
        return dates;
654
}
655
656
QVector<Message> HistorySqlStorage::messages(const Chat &chat, const QDate &date, int limit)
657
{
658
        kdebugf();
659
660
        if (!isDatabaseReady(false))
661
                return QVector<Message>();
662
663
        QMutexLocker locker(&DatabaseMutex);
664
665
        QSqlQuery query(Database);
666
        QString queryString = "SELECT chat.uuid, con.uuid, kmc.content, send_time, receive_time, is_outgoing FROM kadu_messages "
667
                        "LEFT JOIN kadu_chats chat ON (kadu_messages.chat_id=chat.id) "
668
                        "LEFT JOIN kadu_dates d ON (kadu_messages.date_id=d.id) "
669
                        "LEFT JOIN kadu_contacts con ON (kadu_messages.contact_id=con.id) "
670
                        "LEFT JOIN kadu_message_contents kmc ON (kadu_messages.content_id=kmc.id) WHERE " + chatWhere(chat);
671
        if (!date.isNull())
672
                queryString += " AND date = :date";
673
        queryString += " ORDER BY kadu_messages.date_id ASC, kadu_messages.rowid ASC";
674
        if (0 != limit)
675
                queryString += " LIMIT :limit";
676
677
        QVector<Message> messages;
678
        query.prepare(queryString);
679
680
        if (!date.isNull())
681
                query.bindValue(":date", date.toString("yyyyMMdd"));
682
        if (limit != 0)
683
                query.bindValue(":limit", limit);
684
        executeQuery(query);
685
        messages = messagesFromQuery(query);
686
687
        return messages;
688
}
689
690
QVector<Message> HistorySqlStorage::getMessagesSince(const Chat &chat, const QDate &date)
691
{
692
        QMutexLocker locker(&DatabaseMutex);
693
694
        QVector<Message> messages;
695
        if (date.isNull())
696
                return messages;
697
698
        QSqlQuery query(Database);
699
        QString queryString = "SELECT chat.uuid, con.uuid, kmc.content, send_time, receive_time, is_outgoing FROM kadu_messages "
700
                        "LEFT JOIN kadu_chats chat ON (kadu_messages.chat_id=chat.id) "
701
                        "LEFT JOIN kadu_dates d ON (kadu_messages.date_id=d.id) "
702
                        "LEFT JOIN kadu_contacts con ON (kadu_messages.contact_id=con.id) "
703
                        "LEFT JOIN kadu_message_contents kmc ON (kadu_messages.content_id=kmc.id) WHERE " + chatWhere(chat) +
704
                        " AND date >= :date ORDER BY date_id ASC, kadu_messages.rowid ASC";
705
        query.prepare(queryString);
706
707
        query.bindValue(":date", date.toString("yyyyMMdd"));
708
709
        executeQuery(query);
710
711
        messages = messagesFromQuery(query);
712
713
        return messages;
714
}
715
716
QVector<Message> HistorySqlStorage::syncMessagesSince(const Chat &chat, const QDate &date)
717
{
718
        if (!isDatabaseReady(true))
719
                return QVector<Message>();
720
721
        return getMessagesSince(chat, date);
722
}
723
724
QFuture<QVector<Message> > HistorySqlStorage::asyncMessagesSince(const Chat &chat, const QDate &date)
725
{
726
        return QtConcurrent::run(this, &HistorySqlStorage::syncMessagesSince, chat, date);
727
}
728
729
QVector<Message> HistorySqlStorage::getMessagesBackTo(const Chat &chat, const QDateTime &datetime, int limit)
730
{
731
        DatabaseMutex.lock();
732
733
        QVector<Message> result;
734
735
        QSqlQuery query(Database);
736
        // we want last *limit* messages, so we have to invert sorting here
737
        // it is reverted back manually below
738
        QString queryString = "SELECT chat.uuid, con.uuid, kmc.content, send_time, receive_time, is_outgoing FROM kadu_messages "
739
                        "LEFT JOIN kadu_chats chat ON (kadu_messages.chat_id=chat.id) "
740
                        "LEFT JOIN kadu_contacts con ON (kadu_messages.contact_id=con.id) "
741
                        "LEFT JOIN kadu_message_contents kmc ON (kadu_messages.content_id=kmc.id) WHERE " + chatWhere(chat) +
742
                        " AND receive_time >= :datetime ORDER BY date_id DESC, kadu_messages.rowid DESC LIMIT :limit";
743
        query.prepare(queryString);
744
745
        query.bindValue(":datetime", datetime.toString(Qt::ISODate));
746
        query.bindValue(":limit", limit);
747
748
        executeQuery(query);
749
750
        result = messagesFromQuery(query);
751
752
        DatabaseMutex.unlock();
753
754
        // see comment above
755
        QVector<Message> inverted;
756
        inverted.reserve(result.size());
757
758
        for (int i = result.size() - 1; i >= 0; --i)
759
                inverted.append(result.at(i));
760
        return inverted;
761
}
762
763
QVector<Message> HistorySqlStorage::syncGetMessagesBackTo(const Chat &chat, const QDateTime &datetime, int limit)
764
{
765
        if (!isDatabaseReady(true))
766
                return QVector<Message>();
767
768
        return getMessagesBackTo(chat, datetime, limit);
769
}
770
771
QFuture<QVector<Message> > HistorySqlStorage::asyncMessagesBackTo(const Chat &chat, const QDateTime &datetime, int limit)
772
{
773
        return QtConcurrent::run(this, &HistorySqlStorage::syncGetMessagesBackTo, chat, datetime, limit);
774
}
775
776
QList<QString> HistorySqlStorage::smsRecipientsList(const HistorySearchParameters &search)
777
{
778
        kdebugf();
779
780
        if (!isDatabaseReady(false))
781
                return QList<QString>();
782
783
        QMutexLocker locker(&DatabaseMutex);
784
785
        QSqlQuery query(Database);
786
        QString queryString = "SELECT DISTINCT receipient FROM kadu_sms WHERE 1";
787
788
        if (!search.query().isEmpty())
789
                queryString += " AND content LIKE :content";
790
        if (search.fromDate().isValid())
791
                queryString += " AND substr(send_time,0,11)  >= :fromDate";
792
        if (search.toDate().isValid())
793
                queryString += " AND substr(send_time,0,11)  <= :toDate";
794
795
        query.prepare(queryString);
796
797
        if (!search.query().isEmpty())
798
                query.bindValue(":content", QString('%' + search.query() + '%'));
799
        if (search.fromDate().isValid())
800
                query.bindValue(":fromDate", search.fromDate());
801
        if (search.toDate().isValid())
802
                query.bindValue(":toDate", search.toDate());
803
804
        QList<QString> recipients;
805
806
        executeQuery(query);
807
808
        while (query.next())
809
                recipients.append(query.value(0).toString());
810
811
        return recipients;
812
}
813
814
QVector<DatesModelItem> HistorySqlStorage::datesForSmsRecipient(const QString &recipient, const HistorySearchParameters &search)
815
{
816
        kdebugf();
817
818
        if (recipient.isEmpty())
819
                return QVector<DatesModelItem>();
820
821
        if (!isDatabaseReady(false))
822
                return QVector<DatesModelItem>();
823
824
        QMutexLocker locker(&DatabaseMutex);
825
826
        QSqlQuery query(Database);
827
        QString queryString = "SELECT count(1), substr(send_time,0,11)";
828
        queryString += " FROM (SELECT send_time FROM kadu_sms WHERE receipient = :receipient";
829
830
        if (!search.query().isEmpty())
831
                queryString += " AND content LIKE :content";
832
        if (search.fromDate().isValid())
833
                queryString += " AND substr(send_time,0,11) >= :fromDate";
834
        if (search.toDate().isValid())
835
                queryString += " AND substr(send_time,0,11) <= :toDate";
836
837
        queryString += " order by send_time DESC, rowid DESC)";
838
        queryString += " group by substr(send_time,0,11) order by send_time ASC;";
839
840
        query.prepare(queryString);
841
842
        query.bindValue(":receipient", recipient);
843
        if (!search.query().isEmpty())
844
                query.bindValue(":content", QString('%' + search.query() + '%'));
845
        if (search.fromDate().isValid())
846
                query.bindValue(":fromDate", search.fromDate());
847
        if (search.toDate().isValid())
848
                query.bindValue(":toDate", search.toDate());
849
850
        QVector<DatesModelItem> dates;
851
        executeQuery(query);
852
853
        while (query.next())
854
        {
855
                QDate date = query.value(1).toDate();
856
                if (!date.isValid())
857
                        continue;
858
859
                dates.append(DatesModelItem(date, QString(), query.value(0).toInt()));
860
        }
861
862
        return dates;
863
}
864
865
QVector<Message> HistorySqlStorage::sms(const QString &recipient, const QDate &date, int limit)
866
{
867
        kdebugf();
868
869
        if (!isDatabaseReady(false))
870
                return QVector<Message>();
871
872
        QMutexLocker locker(&DatabaseMutex);
873
874
        QSqlQuery query(Database);
875
        QString queryString = "SELECT content, send_time FROM kadu_sms WHERE receipient = :receipient";
876
        if (!date.isNull())
877
                queryString += " AND substr(send_time,0,11) = :date";
878
        queryString += " ORDER BY send_time ASC";
879
        if (0 != limit)
880
                queryString += " LIMIT :limit";
881
882
        query.prepare(queryString);
883
884
        query.bindValue(":receipient", recipient);
885
        if (!date.isNull())
886
                query.bindValue(":date", date.toString(Qt::ISODate));
887
        if (limit != 0)
888
                query.bindValue(":limit", limit);
889
        executeQuery(query);
890
891
        QVector<Message> result = smsFromQuery(query);
892
893
        return result;
894
}
895
896
QVector<Buddy> HistorySqlStorage::statusBuddiesList(const HistorySearchParameters &search)
897
{
898
        kdebugf();
899
900
        if (!isDatabaseReady(false))
901
                return QVector<Buddy>();
902
903
        QMutexLocker locker(&DatabaseMutex);
904
905
        QSqlQuery query(Database);
906
        QString queryString = "SELECT DISTINCT contact FROM kadu_statuses WHERE 1";
907
908
        if (!search.query().isEmpty())
909
                queryString += " AND description LIKE :description";
910
        if (search.fromDate().isValid())
911
                queryString += " AND substr(set_time,0,11) >= :fromDate";
912
        if (search.toDate().isValid())
913
                queryString += " AND substr(set_time,0,11) <= :toDate";
914
915
        query.prepare(queryString);
916
917
        if (!search.query().isEmpty())
918
                query.bindValue(":description", QString('%' + search.query() + '%'));
919
        if (search.fromDate().isValid())
920
                query.bindValue(":fromDate", search.fromDate());
921
        if (search.toDate().isValid())
922
                query.bindValue(":toDate", search.toDate());
923
924
        QVector<Buddy> buddies;
925
926
        executeQuery(query);
927
        while (query.next())
928
        {
929
                Contact contact = ContactManager::instance()->byUuid(query.value(0).toString());
930
                if (!contact)
931
                        continue;
932
933
                Buddy buddy = BuddyManager::instance()->byContact(contact, ActionCreateAndAdd);
934
                Q_ASSERT(buddy);
935
                if (!buddies.contains(buddy))
936
                        buddies.append(buddy);
937
        }
938
939
        return buddies;
940
}
941
942
QVector<DatesModelItem> HistorySqlStorage::datesForStatusBuddy(const Buddy &buddy, const HistorySearchParameters &search)
943
{
944
        kdebugf();
945
946
        if (!buddy)
947
                return QVector<DatesModelItem>();
948
949
        if (!isDatabaseReady(false))
950
                return QVector<DatesModelItem>();
951
952
        QMutexLocker locker(&DatabaseMutex);
953
954
        QSqlQuery query(Database);
955
        QString queryString = "SELECT count(1), substr(set_time,0,11) FROM";
956
        queryString += " (SELECT set_time FROM kadu_statuses WHERE " + buddyContactsWhere(buddy, "contact");
957
958
        if (!search.query().isEmpty())
959
                queryString += " AND description LIKE :description";
960
        if (search.fromDate().isValid())
961
                queryString += " AND substr(set_time,0,11) >= :fromDate";
962
        if (search.toDate().isValid())
963
                queryString += " AND substr(set_time,0,11) <= :toDate";
964
965
        queryString += " ORDER BY set_time DESC, rowid DESC)";
966
        queryString += " GROUP BY substr(set_time,0,11) ORDER BY set_time ASC";
967
968
        query.prepare(queryString);
969
970
        if (!search.query().isEmpty())
971
                query.bindValue(":description", QString('%' + search.query() + '%'));
972
        if (search.fromDate().isValid())
973
                query.bindValue(":fromDate", search.fromDate());
974
        if (search.toDate().isValid())
975
                query.bindValue(":toDate", search.toDate());
976
977
        QVector<DatesModelItem> dates;
978
979
        executeQuery(query);
980
981
        QDate date;
982
        while (query.next())
983
        {
984
                date = query.value(1).toDate();
985
                if (!date.isValid())
986
                        continue;
987
988
                dates.append(DatesModelItem(date, QString(), query.value(0).toInt()));
989
        }
990
991
        return dates;
992
}
993
994
QList<TimedStatus> HistorySqlStorage::statuses(const Buddy &buddy, const QDate &date, int limit)
995
{
996
        kdebugf();
997
998
        if (!isDatabaseReady(false))
999
                return QList<TimedStatus>();
1000
1001
        QMutexLocker locker(&DatabaseMutex);
1002
1003
        QSqlQuery query(Database);
1004
        QString queryString = "SELECT contact, status, description, set_time FROM kadu_statuses WHERE " + buddyContactsWhere(buddy, "contact");
1005
        if (!date.isNull())
1006
                queryString += " AND substr(set_time,0,11) = :date";
1007
        queryString += " ORDER BY set_time ASC";
1008
        if (0 != limit)
1009
                queryString += " LIMIT :limit";
1010
1011
        QList<TimedStatus> statuses;
1012
        query.prepare(queryString);
1013
1014
        if (!date.isNull())
1015
                query.bindValue(":date", date.toString(Qt::ISODate));
1016
        if (limit != 0)
1017
                query.bindValue(":limit", limit);
1018
1019
        executeQuery(query);
1020
        statuses = statusesFromQuery(query);
1021
1022
        return statuses;
1023
}
1024
1025
void HistorySqlStorage::executeQuery(QSqlQuery &query)
1026
{
1027
        kdebugf();
1028
1029
        query.setForwardOnly(true);
1030
1031
        QDateTime before = QDateTime::currentDateTime();
1032
        query.exec();
1033
        QDateTime after = QDateTime::currentDateTime();
1034
        kdebugm(KDEBUG_INFO, "db query: %s\n", qPrintable(query.executedQuery()));
1035
1036
/*
1037
        printf("[%s]\n[%d.%d]-[%d.%d]/%d.%d\n", qPrintable(query.executedQuery()),
1038
                        before.toTime_t(), before.time().msec(),
1039
                        after.toTime_t(), after.time().msec(),
1040
                        after.toTime_t() - before.toTime_t(),
1041
                        after.time().msec() - before.time().msec());
1042
*/
1043
}
1044
1045
QString HistorySqlStorage::stripAllScriptTags(const QString &string)
1046
{
1047
        QString beforeReplace = string;
1048
        QString afterReplace = beforeReplace;
1049
1050
        afterReplace.replace("<script", "", Qt::CaseInsensitive);
1051
        while (beforeReplace != afterReplace)
1052
        {
1053
                beforeReplace = afterReplace;
1054
                afterReplace.replace("<script", "", Qt::CaseInsensitive);
1055
        }
1056
1057
        return afterReplace;
1058
}
1059
1060
QVector<Message> HistorySqlStorage::messagesFromQuery(QSqlQuery &query)
1061
{
1062
        QVector<Message> messages;
1063
        while (query.next())
1064
        {
1065
                bool outgoing = query.value(5).toBool();
1066
1067
                Chat chat = ChatManager::instance()->byUuid(query.value(0).toString());
1068
                if (chat.isNull())
1069
                        continue;
1070
1071
                MessageType type = outgoing ? MessageTypeSent : MessageTypeReceived;
1072
1073
                // ignore non-existing contacts
1074
                Contact sender = ContactManager::instance()->byUuid(query.value(1).toString());
1075
                if (sender.isNull())
1076
                        continue;
1077
1078
                Message message = Message::create();
1079
                message.setMessageChat(chat);
1080
                message.setType(type);
1081
                message.setMessageSender(sender);
1082
                message.setContent(stripAllScriptTags(query.value(2).toString()));
1083
                message.setSendDate(query.value(3).toDateTime());
1084
                message.setReceiveDate(query.value(4).toDateTime());
1085
                message.setStatus(outgoing ? MessageStatusDelivered : MessageStatusReceived);
1086
1087
                messages.append(message);
1088
        }
1089
1090
        return messages;
1091
}
1092
1093
QList<TimedStatus> HistorySqlStorage::statusesFromQuery(QSqlQuery &query)
1094
{
1095
        QList<TimedStatus> statuses;
1096
1097
        while (query.next())
1098
        {
1099
                // ignore non-existing contacts
1100
                Contact sender = ContactManager::instance()->byUuid(query.value(0).toString());
1101
                if (sender.isNull())
1102
                        continue;
1103
1104
                Status status;
1105
                status.setType(StatusTypeManager::instance()->fromName(query.value(1).toString()));
1106
                status.setDescription(Qt::escape(query.value(2).toString()));
1107
1108
                TimedStatus timedStatus(status, query.value(3).toDateTime());
1109
1110
                statuses.append(timedStatus);
1111
        }
1112
1113
        return statuses;
1114
}
1115
1116
QVector<Message> HistorySqlStorage::smsFromQuery(QSqlQuery &query)
1117
{
1118
        QVector<Message> messages;
1119
1120
        while (query.next())
1121
        {
1122
                Message message = Message::create();
1123
                message.setStatus(MessageStatusSent);
1124
                message.setType(MessageTypeSystem);
1125
                message.setReceiveDate(query.value(1).toDateTime());
1126
                message.setSendDate(query.value(1).toDateTime());
1127
                message.setContent(Qt::escape(query.value(0).toString()));
1128
1129
                messages.append(message);
1130
        }
1131
1132
        return messages;
1133
}