File indexing completed on 2025-02-16 04:59:06

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "SQLCache.h"
0024 #include <QSqlError>
0025 #include <QSqlRecord>
0026 #include <QTimer>
0027 #include "Common/SqlTransactionAutoAborter.h"
0028 
0029 //#define CACHE_DEBUG
0030 
0031 namespace
0032 {
0033 static int streamVersion = QDataStream::Qt_4_6;
0034 }
0035 
0036 namespace Imap
0037 {
0038 namespace Mailbox
0039 {
0040 
0041 // This is an arbitrary cutoff point against which we're storing the offset in the database.
0042 // It cannot change without bumping the DB version (but probably shouldn't change at all, at least
0043 // I cannot imagine a reason why that would be necessary).
0044 //
0045 // Yes, we have just invented our own date storage format. That sucks.
0046 // Now, I'm afraid I actually do have an excuse for it. First of all, we're only interested in the data with a rather
0047 // coarse granularity, so we don't really need a type like QDateTime, QDate is enough. However, there's no explicit
0048 // mapping for it in the QtSql's manual page, so we either have to rely on implicit conversions (which don't appear to
0049 // be documented anywhere) or code it ourselves (which is what we do).
0050 //
0051 // Now, an integer looks like a reasonably efficient implementation, so we just have to come up with some cutoff time.
0052 // At first, I wanted to use something "well known" like 1970-01-01, but on a second thought, that looks rather confusing
0053 // -- it's the start of the posix epoch, but the time difference we're storing is in days, not seconds, so it is really
0054 // different from the usual Posix' semantics. That leads me to a conclusion that choosing a "recent" value (at the time this
0055 // was written) is a reasonable approach after all.
0056 //
0057 // But even though the whole code manipulating these data is just a few lines in size, I'm sure there must be a bug lurking
0058 // somewehere -- because there is one each time one codes date-specific functions once again. Please let me know if you're
0059 // hit by it; I apologise in advance.
0060 QDate SQLCache::accessingThresholdDate = QDate(2012, 11, 1);
0061 
0062 SQLCache::SQLCache()
0063     : inTransaction(false)
0064     , m_updateAccessIfOlder(0)
0065 {
0066 }
0067 
0068 void SQLCache::init()
0069 {
0070 #ifdef CACHE_DEBUG
0071     qDebug() << "SQLCache::init()";
0072 #endif
0073     delayedCommit.reset(new QTimer());
0074     delayedCommit->setInterval(10000);
0075     delayedCommit->setObjectName(QStringLiteral("delayedCommit"));
0076     QObject::connect(delayedCommit.get(), &QTimer::timeout,
0077                      delayedCommit.get(), [this](){ this->timeToCommit(); });
0078     tooMuchTimeWithoutCommit.reset(new QTimer());
0079     tooMuchTimeWithoutCommit->setInterval(60000);
0080     tooMuchTimeWithoutCommit->setObjectName(QStringLiteral("tooMuchTimeWithoutCommit"));
0081     QObject::connect(tooMuchTimeWithoutCommit.get(), &QTimer::timeout,
0082                      tooMuchTimeWithoutCommit.get(), [this](){ this->timeToCommit(); });
0083 }
0084 
0085 SQLCache::~SQLCache()
0086 {
0087     timeToCommit();
0088     db.close();
0089 }
0090 
0091 #define TROJITA_SQL_CACHE_CREATE_THREADING \
0092 if ( ! q.exec( QLatin1String("CREATE TABLE msg_threading ( " \
0093                              "mailbox STRING NOT NULL PRIMARY KEY, " \
0094                              "threading BINARY" \
0095                              " )") ) ) { \
0096     emitError( QObject::tr("Can't create table msg_threading"), q ); \
0097     return false; \
0098 }
0099 
0100 #define TROJITA_SQL_CACHE_CREATE_SYNC_STATE \
0101 if ( ! q.exec( QLatin1String("CREATE TABLE mailbox_sync_state ( " \
0102                              "mailbox STRING NOT NULL PRIMARY KEY, " \
0103                              "sync_state BINARY " \
0104                              " )") ) ) { \
0105     emitError( QObject::tr("Can't create table mailbox_sync_state"), q ); \
0106     return false; \
0107 }
0108 
0109 #define TROJITA_SQL_CACHE_CREATE_MSG_METADATA \
0110     if (! q.exec(QLatin1String("CREATE TABLE msg_metadata (" \
0111                                "mailbox STRING NOT NULL, " \
0112                                "uid INT NOT NULL, " \
0113                                "data BINARY, " \
0114                                "lastAccessDate INT, " \
0115                                "PRIMARY KEY (mailbox, uid)" \
0116                                ")"))) { \
0117         emitError(QObject::tr("Can't create table msg_metadata"), q); \
0118         return false; \
0119     }
0120 
0121 bool SQLCache::open(const QString &name, const QString &fileName)
0122 {
0123 #ifdef CACHE_DEBUG
0124     qDebug() << "SQLCache::open()";
0125 #endif
0126     db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), name);
0127     m_cleanup.name = name;
0128     db.setDatabaseName(fileName);
0129 
0130     bool ok = db.open();
0131     if (! ok) {
0132         emitError(QObject::tr("Can't open database"), db);
0133         return false;
0134     }
0135 
0136     Common::SqlTransactionAutoAborter txn(&db);
0137 
0138     QSqlRecord trojitaNames = db.record(QStringLiteral("trojita"));
0139     if (! trojitaNames.contains(QStringLiteral("version"))) {
0140         if (! createTables())
0141             return false;
0142     }
0143 
0144     QSqlQuery q(QString(), db);
0145 
0146     if (! q.exec(QStringLiteral("SELECT version FROM trojita"))) {
0147         emitError(QObject::tr("Failed to verify version"), q);
0148         return false;
0149     }
0150 
0151     if (! q.first()) {
0152         // we could probably relax this...
0153         emitError(QObject::tr("Can't determine version info"), q);
0154         return false;
0155     }
0156 
0157     uint version = q.value(0).toUInt();
0158     if (version == 1) {
0159         TROJITA_SQL_CACHE_CREATE_THREADING
0160         version = 2;
0161         if (! q.exec(QStringLiteral("UPDATE trojita SET version = 2;"))) {
0162             emitError(QObject::tr("Failed to update cache DB scheme from v1 to v2"), q);
0163             return false;
0164         }
0165     }
0166 
0167     if (version == 2 || version == 3) {
0168         // There's no difference in table layout between v3 and v4, but the mailbox_sync_state has changed due to the new
0169         // HIGHESTMODSEQ in Mailbox::SyncState, which is why we throw away the old data unconditionally
0170         if (!q.exec(QStringLiteral("DROP TABLE mailbox_sync_state;"))) {
0171             emitError(QObject::tr("Failed to drop old table mailbox_sync_state"));
0172             return false;
0173         }
0174         TROJITA_SQL_CACHE_CREATE_SYNC_STATE;
0175         version = 4;
0176         if (! q.exec(QStringLiteral("UPDATE trojita SET version = 4;"))) {
0177             emitError(QObject::tr("Failed to update cache DB scheme from v2/v3 to v4"), q);
0178             return false;
0179         }
0180     }
0181 
0182     if (version == 4 || version == 5 || version == 6) {
0183         // No difference in table structure between v4 and v5, but the data stored in msg_metadata is different; the UID
0184         // got removed and INTERNALDATE was added.
0185         // V6 has added the References and List-Post headers (i.e. a change in the structure of the blobs stored in the DB,
0186         // but transparent on the SQL level), and also changed the DB structure by adding a date specifying how recently
0187         // a given message was accessed (which was needed for cache lifetime management).
0188         // V7 changed the sizes to quint64 (from uint), so the message metadata again changed
0189         if (!q.exec(QStringLiteral("DROP TABLE msg_metadata;"))) {
0190             emitError(QObject::tr("Failed to drop old table msg_metadata"));
0191             return false;
0192         }
0193         TROJITA_SQL_CACHE_CREATE_MSG_METADATA;
0194         version = 7;
0195         if (! q.exec(QStringLiteral("UPDATE trojita SET version = 7;"))) {
0196             emitError(QObject::tr("Failed to update cache DB scheme from v4/v5/v6 to v7"), q);
0197             return false;
0198         }
0199     }
0200 
0201     if (version != 7) {
0202         emitError(QObject::tr("Unknown version of sqlite cache"));
0203         return false;
0204     }
0205 
0206     txn.commit();
0207 
0208     if (! prepareQueries()) {
0209         return false;
0210     }
0211     init();
0212 #ifdef CACHE_DEBUG
0213     qDebug() << "SQLCache::open() succeeded";
0214 #endif
0215     return true;
0216 }
0217 
0218 bool SQLCache::createTables()
0219 {
0220     QSqlQuery q(QString(), db);
0221 
0222     if (! q.exec(QStringLiteral("CREATE TABLE trojita ( version STRING NOT NULL )"))) {
0223         emitError(QObject::tr("Failed to prepare table structures"), q);
0224         return false;
0225     }
0226     if (! q.exec(QStringLiteral("INSERT INTO trojita ( version ) VALUES ( 6 )"))) {
0227         emitError(QObject::tr("Can't store version info"), q);
0228         return false;
0229     }
0230     if (! q.exec(QStringLiteral(
0231                      "CREATE TABLE child_mailboxes ( "
0232                      "mailbox STRING NOT NULL PRIMARY KEY, "
0233                      "parent STRING NOT NULL, "
0234                      "separator STRING, "
0235                      "flags BINARY"
0236                      ")"
0237                  ))) {
0238         emitError(QObject::tr("Can't create table child_mailboxes"));
0239         return false;
0240     }
0241 
0242     if (! q.exec(QStringLiteral("CREATE TABLE uid_mapping ( "
0243                                "mailbox STRING NOT NULL PRIMARY KEY, "
0244                                "mapping BINARY"
0245                                " )"))) {
0246         emitError(QObject::tr("Can't create table uid_mapping"), q);
0247         return false;
0248     }
0249 
0250     TROJITA_SQL_CACHE_CREATE_MSG_METADATA;
0251 
0252     if (! q.exec(QStringLiteral("CREATE TABLE flags ("
0253                                "mailbox STRING NOT NULL, "
0254                                "uid INT NOT NULL, "
0255                                "flags BINARY, "
0256                                "PRIMARY KEY (mailbox, uid)"
0257                                ")"))) {
0258         emitError(QObject::tr("Can't create table flags"), q);
0259     }
0260 
0261     if (! q.exec(QStringLiteral("CREATE TABLE parts ("
0262                                "mailbox STRING NOT NULL, "
0263                                "uid INT NOT NULL, "
0264                                "part_id BINARY, "
0265                                "data BINARY, "
0266                                "PRIMARY KEY (mailbox, uid, part_id)"
0267                                ")"))) {
0268         emitError(QObject::tr("Can't create table parts"), q);
0269     }
0270 
0271     TROJITA_SQL_CACHE_CREATE_THREADING;
0272     TROJITA_SQL_CACHE_CREATE_SYNC_STATE;
0273 
0274     return true;
0275 }
0276 
0277 bool SQLCache::prepareQueries()
0278 {
0279     queryChildMailboxes = QSqlQuery(db);
0280     if (! queryChildMailboxes.prepare(QStringLiteral("SELECT mailbox, separator, flags FROM child_mailboxes WHERE parent = ?"))) {
0281         emitError(QObject::tr("Failed to prepare queryChildMailboxes"), queryChildMailboxes);
0282         return false;
0283     }
0284 
0285     queryChildMailboxesFresh = QSqlQuery(db);
0286     if (! queryChildMailboxesFresh.prepare(QStringLiteral("SELECT mailbox FROM child_mailboxes WHERE parent = ? LIMIT 1"))) {
0287         emitError(QObject::tr("Failed to prepare queryChildMailboxesFresh"), queryChildMailboxesFresh);
0288         return false;
0289     }
0290 
0291     queryRemoveChildMailboxes = QSqlQuery(db);
0292     if (!queryRemoveChildMailboxes.prepare(QStringLiteral("DELETE FROM child_mailboxes WHERE parent = ?"))) {
0293         emitError(QObject::tr("Failed to prepare queryRemoveChildMailboxes"), queryRemoveChildMailboxes);
0294         return false;
0295     }
0296 
0297     querySetChildMailboxes = QSqlQuery(db);
0298     if (! querySetChildMailboxes.prepare(QStringLiteral("INSERT OR REPLACE INTO child_mailboxes ( mailbox, parent, separator, flags ) VALUES (?, ?, ?, ?)"))) {
0299         emitError(QObject::tr("Failed to prepare querySetChildMailboxes"), querySetChildMailboxes);
0300         return false;
0301     }
0302 
0303     queryMailboxSyncState = QSqlQuery(db);
0304     if (! queryMailboxSyncState.prepare(QStringLiteral("SELECT sync_state FROM mailbox_sync_state WHERE mailbox = ?"))) {
0305         emitError(QObject::tr("Failed to prepare queryMailboxSyncState"), queryMailboxSyncState);
0306         return false;
0307     }
0308 
0309     querySetMailboxSyncState = QSqlQuery(db);
0310     if (! querySetMailboxSyncState.prepare(QStringLiteral("INSERT OR REPLACE INTO mailbox_sync_state "
0311                                            "( mailbox, sync_state ) "
0312                                            "VALUES ( ?, ? )"))) {
0313         emitError(QObject::tr("Failed to prepare querySetMailboxSyncState"), querySetMailboxSyncState);
0314         return false;
0315     }
0316 
0317     queryUidMapping = QSqlQuery(db);
0318     if (! queryUidMapping.prepare(QStringLiteral("SELECT mapping FROM uid_mapping WHERE mailbox = ?"))) {
0319         emitError(QObject::tr("Failed to prepare queryUidMapping"), queryUidMapping);
0320         return false;
0321     }
0322 
0323     querySetUidMapping = QSqlQuery(db);
0324     if (! querySetUidMapping.prepare(QStringLiteral("INSERT OR REPLACE INTO uid_mapping (mailbox, mapping) VALUES  ( ?, ? )"))) {
0325         emitError(QObject::tr("Failed to prepare querySetUidMapping"), querySetUidMapping);
0326         return false;
0327     }
0328 
0329     queryClearUidMapping = QSqlQuery(db);
0330     if (! queryClearUidMapping.prepare(QStringLiteral("DELETE FROM uid_mapping WHERE mailbox = ?"))) {
0331         emitError(QObject::tr("Failed to prepare queryClearUidMapping"), queryClearUidMapping);
0332         return false;
0333     }
0334 
0335     queryMessageMetadata = QSqlQuery(db);
0336     if (! queryMessageMetadata.prepare(QStringLiteral("SELECT data, lastAccessDate FROM msg_metadata WHERE mailbox = ? AND uid = ?"))) {
0337         emitError(QObject::tr("Failed to prepare queryMessageMetadata"), queryMessageMetadata);
0338         return false;
0339     }
0340 
0341     queryAccessMessageMetadata = QSqlQuery(db);
0342     if (!queryAccessMessageMetadata.prepare(QStringLiteral("UPDATE msg_metadata SET lastAccessDate = ? WHERE mailbox = ? AND uid = ?"))) {
0343         emitError(QObject::tr("Failed to prepare queryAccssMessageMetadata"), queryAccessMessageMetadata);
0344         return false;
0345     }
0346 
0347     querySetMessageMetadata = QSqlQuery(db);
0348     if (! querySetMessageMetadata.prepare(QStringLiteral("INSERT OR REPLACE INTO msg_metadata ( mailbox, uid, data, lastAccessDate ) VALUES ( ?, ?, ?, ? )"))) {
0349         emitError(QObject::tr("Failed to prepare querySetMessageMetadata"), querySetMessageMetadata);
0350         return false;
0351     }
0352 
0353     queryMessageFlags = QSqlQuery(db);
0354     if (! queryMessageFlags.prepare(QStringLiteral("SELECT flags FROM flags WHERE mailbox = ? AND uid = ?"))) {
0355         emitError(QObject::tr("Failed to prepare queryMessageFlags"), queryMessageFlags);
0356         return false;
0357     }
0358 
0359     querySetMessageFlags = QSqlQuery(db);
0360     if (! querySetMessageFlags.prepare(QStringLiteral("INSERT OR REPLACE INTO flags ( mailbox, uid, flags ) VALUES ( ?, ?, ? )"))) {
0361         emitError(QObject::tr("Failed to prepare querySetMessageFlags"), querySetMessageFlags);
0362         return false;
0363     }
0364 
0365     queryClearAllMessages1 = QSqlQuery(db);
0366     if (! queryClearAllMessages1.prepare(QStringLiteral("DELETE FROM msg_metadata WHERE mailbox = ?"))) {
0367         emitError(QObject::tr("Failed to prepare queryClearAllMessages1"), queryClearAllMessages1);
0368         return false;
0369     }
0370 
0371     queryClearAllMessages2 = QSqlQuery(db);
0372     if (! queryClearAllMessages2.prepare(QStringLiteral("DELETE FROM flags WHERE mailbox = ?"))) {
0373         emitError(QObject::tr("Failed to prepare queryClearAllMessages2"), queryClearAllMessages2);
0374         return false;
0375     }
0376 
0377     queryClearAllMessages3 = QSqlQuery(db);
0378     if (! queryClearAllMessages3.prepare(QStringLiteral("DELETE FROM parts WHERE mailbox = ?"))) {
0379         emitError(QObject::tr("Failed to prepare queryClearAllMessages3"), queryClearAllMessages3);
0380         return false;
0381     }
0382 
0383     queryClearAllMessages4 = QSqlQuery(db);
0384     if (! queryClearAllMessages4.prepare(QStringLiteral("DELETE FROM msg_threading WHERE mailbox = ?"))) {
0385         emitError(QObject::tr("Failed to prepare queryClearAllMessages4"), queryClearAllMessages4);
0386         return false;
0387     }
0388 
0389     queryClearMessage1 = QSqlQuery(db);
0390     if (! queryClearMessage1.prepare(QStringLiteral("DELETE FROM msg_metadata WHERE mailbox = ? AND uid = ?"))) {
0391         emitError(QObject::tr("Failed to prepare queryClearMessage1"), queryClearMessage1);
0392         return false;
0393     }
0394 
0395     queryClearMessage2 = QSqlQuery(db);
0396     if (! queryClearMessage2.prepare(QStringLiteral("DELETE FROM flags WHERE mailbox = ? AND uid = ?"))) {
0397         emitError(QObject::tr("Failed to prepare queryClearMessage2"), queryClearMessage2);
0398         return false;
0399     }
0400 
0401     queryClearMessage3 = QSqlQuery(db);
0402     if (! queryClearMessage3.prepare(QStringLiteral("DELETE FROM parts WHERE mailbox = ? AND uid = ?"))) {
0403         emitError(QObject::tr("Failed to prepare queryClearMessage3"), queryClearMessage3);
0404         return false;
0405     }
0406 
0407     queryMessagePart = QSqlQuery(db);
0408     if (! queryMessagePart.prepare(QStringLiteral("SELECT data FROM parts WHERE mailbox = ? AND uid = ? AND part_id = ?"))) {
0409         emitError(QObject::tr("Failed to prepare queryMessagePart"), queryMessagePart);
0410         return false;
0411     }
0412 
0413     querySetMessagePart = QSqlQuery(db);
0414     if (! querySetMessagePart.prepare(QStringLiteral("INSERT OR REPLACE INTO parts ( mailbox, uid, part_id, data ) VALUES (?, ?, ?, ?)"))) {
0415         emitError(QObject::tr("Failed to prepare querySetMessagePart"), querySetMessagePart);
0416         return false;
0417     }
0418 
0419     queryForgetMessagePart = QSqlQuery(db);
0420     if (! queryForgetMessagePart.prepare(QStringLiteral("DELETE FROM parts WHERE mailbox = ? AND uid = ? AND part_id = ?"))) {
0421         emitError(QObject::tr("Failed to prepare queryForgetMessagePart"), queryForgetMessagePart);
0422         return false;
0423     }
0424 
0425     queryMessageThreading = QSqlQuery(db);
0426     if (! queryMessageThreading.prepare(QStringLiteral("SELECT threading FROM msg_threading WHERE mailbox = ?"))) {
0427         emitError(QObject::tr("Failed to prepare queryMessageThreading"), queryMessageThreading);
0428         return false;
0429     }
0430 
0431     querySetMessageThreading = QSqlQuery(db);
0432     if (! querySetMessageThreading.prepare(QStringLiteral("INSERT OR REPLACE INTO msg_threading (mailbox, threading) VALUES  ( ?, ? )"))) {
0433         emitError(QObject::tr("Failed to prepare querySetMessageThreading"), querySetMessageThreading);
0434         return false;
0435     }
0436 
0437 #ifdef CACHE_DEBUG
0438     qDebug() << "SQLCache::_prepareQueries() succeeded";
0439 #endif
0440     return true;
0441 }
0442 
0443 void SQLCache::emitError(const QString &message, const QSqlQuery &query) const
0444 {
0445     emitError(QStringLiteral("SQLCache: Query Error: %1: %2").arg(message, query.lastError().text()));
0446 }
0447 
0448 void SQLCache::emitError(const QString &message, const QSqlDatabase &database) const
0449 {
0450     emitError(QStringLiteral("SQLCache: DB Error: %1: %2").arg(message, database.lastError().text()));
0451 }
0452 
0453 void SQLCache::emitError(const QString &message) const
0454 {
0455     qDebug() << message;
0456     m_errorHandler(message);
0457 }
0458 
0459 QList<MailboxMetadata> SQLCache::childMailboxes(const QString &mailbox) const
0460 {
0461     QList<MailboxMetadata> res;
0462     queryChildMailboxes.bindValue(0, mailboxName(mailbox));
0463     if (! queryChildMailboxes.exec()) {
0464         emitError(QObject::tr("Query queryChildMailboxes failed"), queryChildMailboxes);
0465         return res;
0466     }
0467     while (queryChildMailboxes.next()) {
0468         MailboxMetadata item;
0469         item.mailbox = queryChildMailboxes.value(0).toString();
0470         item.separator = queryChildMailboxes.value(1).toString();
0471         QDataStream stream(queryChildMailboxes.value(2).toByteArray());
0472         stream.setVersion(streamVersion);
0473         stream >> item.flags;
0474         if (stream.status() != QDataStream::Ok) {
0475             emitError(QObject::tr("Corrupt data when reading child items for mailbox %1, line %2").arg(mailbox, item.mailbox));
0476             return QList<MailboxMetadata>();
0477         }
0478         res << item;
0479     }
0480     return res;
0481 }
0482 
0483 bool SQLCache::childMailboxesFresh(const QString &mailbox) const
0484 {
0485     queryChildMailboxesFresh.bindValue(0, mailboxName(mailbox));
0486     if (! queryChildMailboxesFresh.exec()) {
0487         emitError(QObject::tr("Query queryChildMailboxesFresh failed"), queryChildMailboxesFresh);
0488         return false;
0489     }
0490     return queryChildMailboxesFresh.first();
0491 }
0492 
0493 void SQLCache::setChildMailboxes(const QString &mailbox, const QList<MailboxMetadata> &data)
0494 {
0495 #ifdef CACHE_DEBUG
0496     qDebug() << "Setting child mailboxes for" << mailbox;
0497 #endif
0498     touchingDB();
0499     QVariantList mailboxFields, parentFields, separatorFields, flagsFelds;
0500     Q_FOREACH(const MailboxMetadata& item, data) {
0501         mailboxFields << item.mailbox;
0502         parentFields << mailboxName(mailbox);
0503         separatorFields << item.separator;
0504         QByteArray buf;
0505         QDataStream stream(&buf, QIODevice::ReadWrite);
0506         stream.setVersion(streamVersion);
0507         stream << item.flags;
0508         flagsFelds << buf;
0509     }
0510     queryRemoveChildMailboxes.bindValue(0, mailboxName(mailbox));
0511     if (!queryRemoveChildMailboxes.exec()) {
0512         emitError(QObject::tr("Query queryRemoveChildMailboxes failed"), queryRemoveChildMailboxes);
0513         return;
0514     }
0515     querySetChildMailboxes.bindValue(0, mailboxFields);
0516     querySetChildMailboxes.bindValue(1, parentFields);
0517     querySetChildMailboxes.bindValue(2, separatorFields);
0518     querySetChildMailboxes.bindValue(3, flagsFelds);
0519     if (! querySetChildMailboxes.execBatch()) {
0520         emitError(QObject::tr("Query querySetChildMailboxes failed"), querySetChildMailboxes);
0521         return;
0522     }
0523 }
0524 
0525 SyncState SQLCache::mailboxSyncState(const QString &mailbox) const
0526 {
0527     SyncState res;
0528     queryMailboxSyncState.bindValue(0, mailboxName(mailbox));
0529     if (! queryMailboxSyncState.exec()) {
0530         emitError(QObject::tr("Query queryMailboxSyncState failed"), queryMailboxSyncState);
0531         return res;
0532     }
0533     if (queryMailboxSyncState.first()) {
0534         QDataStream stream(queryMailboxSyncState.value(0).toByteArray());
0535         stream.setVersion(streamVersion);
0536         stream >> res;
0537     }
0538     // "No data present" doesn't necessarily imply a problem -- it simply might not be there yet :)
0539     return res;
0540 }
0541 
0542 void SQLCache::setMailboxSyncState(const QString &mailbox, const SyncState &state)
0543 {
0544 #ifdef CACHE_DEBUG
0545     qDebug() << "Setting sync state for" << mailbox;
0546 #endif
0547     touchingDB();
0548     querySetMailboxSyncState.bindValue(0, mailboxName(mailbox));
0549     QByteArray buf;
0550     QDataStream stream(&buf, QIODevice::ReadWrite);
0551     stream.setVersion(streamVersion);
0552     stream << state;
0553     querySetMailboxSyncState.bindValue(1, buf);
0554     if (! querySetMailboxSyncState.exec()) {
0555         emitError(QObject::tr("Query querySetMailboxSyncState failed"), querySetMailboxSyncState);
0556         return;
0557     }
0558 }
0559 
0560 Imap::Uids SQLCache::uidMapping(const QString &mailbox) const
0561 {
0562     Imap::Uids res;
0563     queryUidMapping.bindValue(0, mailboxName(mailbox));
0564     if (! queryUidMapping.exec()) {
0565         emitError(QObject::tr("Query queryUidMapping failed"), queryUidMapping);
0566         return res;
0567     }
0568     if (queryUidMapping.first()) {
0569         QDataStream stream(qUncompress(queryUidMapping.value(0).toByteArray()));
0570         stream.setVersion(streamVersion);
0571         stream >> res;
0572     }
0573     // "No data present" doesn't necessarily imply a problem -- it simply might not be there yet :)
0574     return res;
0575 }
0576 
0577 void SQLCache::setUidMapping(const QString &mailbox, const Imap::Uids &seqToUid)
0578 {
0579 #ifdef CACHE_DEBUG
0580     qDebug() << "Setting UID mapping for" << mailbox;
0581 #endif
0582     touchingDB();
0583     querySetUidMapping.bindValue(0, mailboxName(mailbox));
0584     QByteArray buf;
0585     QDataStream stream(&buf, QIODevice::ReadWrite);
0586     stream.setVersion(streamVersion);
0587     stream << seqToUid;
0588     querySetUidMapping.bindValue(1, qCompress(buf));
0589     if (! querySetUidMapping.exec()) {
0590         emitError(QObject::tr("Query querySetUidMapping failed"), querySetUidMapping);
0591     }
0592 }
0593 
0594 void SQLCache::clearUidMapping(const QString &mailbox)
0595 {
0596 #ifdef CACHE_DEBUG
0597     qDebug() << "Clearing UID mapping for" << mailbox;
0598 #endif
0599     touchingDB();
0600     queryClearUidMapping.bindValue(0, mailboxName(mailbox));
0601     if (! queryClearUidMapping.exec()) {
0602         emitError(QObject::tr("Query queryClearUidMapping failed"), queryClearUidMapping);
0603     }
0604 }
0605 
0606 void SQLCache::clearAllMessages(const QString &mailbox)
0607 {
0608 #ifdef CACHE_DEBUG
0609     qDebug() << "Clearing all messages from" << mailbox;
0610 #endif
0611     touchingDB();
0612     queryClearAllMessages1.bindValue(0, mailboxName(mailbox));
0613     queryClearAllMessages2.bindValue(0, mailboxName(mailbox));
0614     queryClearAllMessages3.bindValue(0, mailboxName(mailbox));
0615     queryClearAllMessages4.bindValue(0, mailboxName(mailbox));
0616     if (! queryClearAllMessages1.exec()) {
0617         emitError(QObject::tr("Query queryClearAllMessages1 failed"), queryClearAllMessages1);
0618     }
0619     if (! queryClearAllMessages2.exec()) {
0620         emitError(QObject::tr("Query queryClearAllMessages2 failed"), queryClearAllMessages2);
0621     }
0622     if (! queryClearAllMessages3.exec()) {
0623         emitError(QObject::tr("Query queryClearAllMessages3 failed"), queryClearAllMessages3);
0624     }
0625     if (! queryClearAllMessages4.exec()) {
0626         emitError(QObject::tr("Query queryClearAllMessages4 failed"), queryClearAllMessages4);
0627     }
0628     clearUidMapping(mailbox);
0629 }
0630 
0631 void SQLCache::clearMessage(const QString mailbox, uint uid)
0632 {
0633 #ifdef CACHE_DEBUG
0634     qDebug() << "Clearing message" << uid << "from" << mailbox;
0635 #endif
0636     touchingDB();
0637     queryClearMessage1.bindValue(0, mailboxName(mailbox));
0638     queryClearMessage1.bindValue(1, uid);
0639     queryClearMessage2.bindValue(0, mailboxName(mailbox));
0640     queryClearMessage2.bindValue(1, uid);
0641     queryClearMessage3.bindValue(0, mailboxName(mailbox));
0642     queryClearMessage3.bindValue(1, uid);
0643     if (! queryClearMessage1.exec()) {
0644         emitError(QObject::tr("Query queryClearMessage1 failed"), queryClearMessage1);
0645     }
0646     if (! queryClearMessage2.exec()) {
0647         emitError(QObject::tr("Query queryClearMessage2 failed"), queryClearMessage2);
0648     }
0649     if (! queryClearMessage3.exec()) {
0650         emitError(QObject::tr("Query queryClearMessage3 failed"), queryClearMessage3);
0651     }
0652 }
0653 
0654 QStringList SQLCache::msgFlags(const QString &mailbox, const uint uid) const
0655 {
0656     QStringList res;
0657     queryMessageFlags.bindValue(0, mailboxName(mailbox));
0658     queryMessageFlags.bindValue(1, uid);
0659     if (! queryMessageFlags.exec()) {
0660         emitError(QObject::tr("Query queryMessageFlags failed"), queryMessageFlags);
0661         return res;
0662     }
0663     if (queryMessageFlags.first()) {
0664         QDataStream stream(queryMessageFlags.value(0).toByteArray());
0665         stream.setVersion(streamVersion);
0666         stream >> res;
0667     }
0668     // "Not found" is not an error here
0669     return res;
0670 }
0671 
0672 void SQLCache::setMsgFlags(const QString &mailbox, const uint uid, const QStringList &flags)
0673 {
0674 #ifdef CACHE_DEBUG
0675     qDebug() << "Updating flags for" << mailbox << uid;
0676 #endif
0677     touchingDB();
0678     querySetMessageFlags.bindValue(0, mailboxName(mailbox));
0679     querySetMessageFlags.bindValue(1, uid);
0680     QByteArray buf;
0681     QDataStream stream(&buf, QIODevice::ReadWrite);
0682     stream.setVersion(streamVersion);
0683     stream << flags;
0684     querySetMessageFlags.bindValue(2, buf);
0685     if (! querySetMessageFlags.exec()) {
0686         emitError(QObject::tr("Query querySetMessageFlags failed"), querySetMessageFlags);
0687     }
0688 }
0689 
0690 AbstractCache::MessageDataBundle SQLCache::messageMetadata(const QString &mailbox, uint uid) const
0691 {
0692     AbstractCache::MessageDataBundle res;
0693     queryMessageMetadata.bindValue(0, mailboxName(mailbox));
0694     queryMessageMetadata.bindValue(1, uid);
0695     if (! queryMessageMetadata.exec()) {
0696         emitError(QObject::tr("Query queryMessageMetadata failed"), queryMessageMetadata);
0697         return res;
0698     }
0699     if (queryMessageMetadata.first()) {
0700         res.uid = uid;
0701         QDataStream stream(qUncompress(queryMessageMetadata.value(0).toByteArray()));
0702         stream.setVersion(streamVersion);
0703         stream >> res.envelope >> res.internalDate >> res.size >> res.serializedBodyStructure >> res.hdrReferences
0704                   >> res.hdrListPost >> res.hdrListPostNo;
0705 
0706         if (m_updateAccessIfOlder) {
0707             int lastAccessTimestamp = queryMessageMetadata.value(1).toInt();
0708             int currentDiff = accessingThresholdDate.daysTo(QDate::currentDate());
0709             if (lastAccessTimestamp < currentDiff - m_updateAccessIfOlder) {
0710                 queryAccessMessageMetadata.bindValue(0, currentDiff);
0711                 queryAccessMessageMetadata.bindValue(1, mailboxName(mailbox));
0712                 queryAccessMessageMetadata.bindValue(2, uid);
0713                 if (!queryAccessMessageMetadata.exec()) {
0714                     emitError(QObject::tr("Query queryAccessMessageMetadata failed"), queryAccessMessageMetadata);
0715                 }
0716             }
0717         }
0718     }
0719     // "Not found" is not an error here
0720     return res;
0721 }
0722 
0723 void SQLCache::setMessageMetadata(const QString &mailbox, const uint uid, const MessageDataBundle &metadata)
0724 {
0725 #ifdef CACHE_DEBUG
0726     qDebug() << "Setting message metadata for" << uid << mailbox;
0727 #endif
0728     touchingDB();
0729     // Order of values: mailbox, uid, data
0730     querySetMessageMetadata.bindValue(0, mailboxName(mailbox));
0731     querySetMessageMetadata.bindValue(1, uid);
0732     QByteArray buf;
0733     QDataStream stream(&buf, QIODevice::ReadWrite);
0734     stream.setVersion(streamVersion);
0735     stream << metadata.envelope << metadata.internalDate << metadata.size << metadata.serializedBodyStructure
0736            << metadata.hdrReferences << metadata.hdrListPost << metadata.hdrListPostNo;
0737     querySetMessageMetadata.bindValue(2, qCompress(buf));
0738     querySetMessageMetadata.bindValue(3, accessingThresholdDate.daysTo(QDate::currentDate()));
0739     if (! querySetMessageMetadata.exec()) {
0740         emitError(QObject::tr("Query querySetMessageMetadata failed"), querySetMessageMetadata);
0741     }
0742 }
0743 
0744 QByteArray SQLCache::messagePart(const QString &mailbox, const uint uid, const QByteArray &partId) const
0745 {
0746     QByteArray res;
0747     queryMessagePart.bindValue(0, mailboxName(mailbox));
0748     queryMessagePart.bindValue(1, uid);
0749     queryMessagePart.bindValue(2, partId);
0750     if (! queryMessagePart.exec()) {
0751         emitError(QObject::tr("Query queryMessagePart failed"), queryMessagePart);
0752         return res;
0753     }
0754     if (queryMessagePart.first()) {
0755         res = qUncompress(queryMessagePart.value(0).toByteArray());
0756         queryMessagePart.finish();
0757     }
0758     return res;
0759 }
0760 
0761 void SQLCache::setMsgPart(const QString &mailbox, const uint uid, const QByteArray &partId, const QByteArray &data)
0762 {
0763 #ifdef CACHE_DEBUG
0764     qDebug() << "Saving message part" << partId << uid << mailbox;
0765 #endif
0766     touchingDB();
0767     querySetMessagePart.bindValue(0, mailboxName(mailbox));
0768     querySetMessagePart.bindValue(1, uid);
0769     querySetMessagePart.bindValue(2, partId);
0770     querySetMessagePart.bindValue(3, qCompress(data));
0771     if (! querySetMessagePart.exec()) {
0772         emitError(QObject::tr("Query querySetMessagePart failed"), querySetMessagePart);
0773     }
0774 }
0775 
0776 void SQLCache::forgetMessagePart(const QString &mailbox, const uint uid, const QByteArray &partId)
0777 {
0778 #ifdef CACHE_DEBUG
0779     qDebug() << "Forgetting message part" << partId << uid << mailbox;
0780 #endif
0781     touchingDB();
0782     queryForgetMessagePart.bindValue(0, mailboxName(mailbox));
0783     queryForgetMessagePart.bindValue(1, uid);
0784     queryForgetMessagePart.bindValue(2, partId);
0785     if (! queryForgetMessagePart.exec()) {
0786         emitError(QObject::tr("Query queryForgetMessagePart failed"), queryForgetMessagePart);
0787     }
0788 }
0789 
0790 QVector<Imap::Responses::ThreadingNode> SQLCache::messageThreading(const QString &mailbox)
0791 {
0792     QVector<Imap::Responses::ThreadingNode> res;
0793     queryMessageThreading.bindValue(0, mailboxName(mailbox));
0794     if (! queryMessageThreading.exec()) {
0795         emitError(QObject::tr("Query queryMessageThreading failed"), queryMessageThreading);
0796         return res;
0797     }
0798     if (queryMessageThreading.first()) {
0799         QDataStream stream(qUncompress(queryMessageThreading.value(0).toByteArray()));
0800         stream.setVersion(streamVersion);
0801         stream >> res;
0802     }
0803     return res;
0804 }
0805 
0806 void SQLCache::setMessageThreading(const QString &mailbox, const QVector<Imap::Responses::ThreadingNode> &threading)
0807 {
0808 #ifdef CACHE_DEBUG
0809     qDebug() << "Setting threading for" << mailbox;
0810 #endif
0811     touchingDB();
0812     querySetMessageThreading.bindValue(0, mailboxName(mailbox));
0813     QByteArray buf;
0814     QDataStream stream(&buf, QIODevice::ReadWrite);
0815     stream.setVersion(streamVersion);
0816     stream << threading;
0817     querySetMessageThreading.bindValue(1, qCompress(buf));
0818     if (! querySetMessageThreading.exec()) {
0819         emitError(QObject::tr("Query querySetMessageThreading failed"), querySetMessageThreading);
0820     }
0821 
0822 }
0823 
0824 void SQLCache::touchingDB()
0825 {
0826     delayedCommit->start();
0827     if (! inTransaction) {
0828 #ifdef CACHE_DEBUG
0829         qDebug() << "Starting transaction";
0830 #endif
0831         inTransaction = true;
0832         db.transaction();
0833         tooMuchTimeWithoutCommit->start();
0834     }
0835 }
0836 
0837 void SQLCache::timeToCommit()
0838 {
0839     if (inTransaction) {
0840 #ifdef CACHE_DEBUG
0841         qDebug() << "Commit";
0842 #endif
0843         inTransaction = false;
0844         db.commit();
0845     }
0846 }
0847 
0848 void SQLCache::setRenewalThreshold(const int days)
0849 {
0850     m_updateAccessIfOlder = days;
0851 }
0852 
0853 /** @short Return a proper represenation of the mailbox name to be used in the SQL queries
0854 
0855 A null QString is represented as NIL, which makes our cache unhappy.
0856 */
0857 QString SQLCache::mailboxName(const QString &mailbox)
0858 {
0859     return mailbox.isEmpty() ? QLatin1String("") : mailbox;
0860 }
0861 
0862 DbConnectionCleanup::~DbConnectionCleanup()
0863 {
0864     QSqlDatabase::removeDatabase(name);
0865 }
0866 
0867 }
0868 }