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 }