File indexing completed on 2024-11-24 04:53:13

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 #ifndef IMAP_MODEL_SQLCACHE_H
0024 #define IMAP_MODEL_SQLCACHE_H
0025 
0026 #include <memory>
0027 #include <QSqlDatabase>
0028 #include <QSqlQuery>
0029 #include "Cache.h"
0030 
0031 class QTimer;
0032 
0033 /** @short Namespace for IMAP interaction */
0034 namespace Imap
0035 {
0036 
0037 /** @short Classes for handling of mailboxes and connections */
0038 namespace Mailbox
0039 {
0040 
0041 /** @short Wrapper around the braindead API of QSqlDatabase for auto-removing connections
0042 
0043 Because the QSqlDatabase really wants to operate over a global namespace of DB connections, it's important to remove them
0044 when they are no longer needed. However, this removal (QSqlDatabase::removeDatabase) should run after all queries are gone,
0045 otherwise there's an ugly warning on stderr about queries which are going to cease to work.
0046 */
0047 struct DbConnectionCleanup
0048 {
0049     ~DbConnectionCleanup();
0050     QString name;
0051 };
0052 
0053 /** @short A cache implementation using an sqlite database for the underlying storage
0054 
0055   This class should not be used on its own, as it simply puts everything into a database.
0056 This is clearly a suboptimal way to store large binary data, like e-mail attachments. The
0057 purpose of this class is therefore to serve as one of a few caching backends, which are
0058 subsequently used by an intelligent cache manager.
0059 
0060 The database layout is aimed at a regular desktop or an embedded device. It certainly is
0061 not meant as a proper database design -- we bundle several columns together when we know
0062 that the API will only access them as a tuple, we use a proprietary compression on them
0063 et cetera. In short, the layout of the database is supposed to act as a quick and dumb
0064 cache and is certainly *not* meant to be accessed by third-party applications. Please, do
0065 consider it an opaque format.
0066 
0067 Some ideas for improvements:
0068 - Don't store full string mailbox names in each table, use another table for it
0069 - Merge uid_mapping with mailbox_sync_state, and also msg_metadata with flags
0070 - Serious embedded users might consider putting the database into a compressed filesystem,
0071   or using on-the-fly compression via sqlite's VFS subsystem
0072 
0073  */
0074 class SQLCache : public AbstractCache
0075 {
0076 public:
0077     SQLCache();
0078     virtual ~SQLCache();
0079 
0080     QList<MailboxMetadata> childMailboxes(const QString &mailbox) const override;
0081     bool childMailboxesFresh(const QString &mailbox) const override;
0082     void setChildMailboxes(const QString &mailbox, const QList<MailboxMetadata> &data) override;
0083 
0084     SyncState mailboxSyncState(const QString &mailbox) const override;
0085     void setMailboxSyncState(const QString &mailbox, const SyncState &state) override;
0086 
0087     void setUidMapping(const QString &mailbox, const Imap::Uids &seqToUid) override;
0088     void clearUidMapping(const QString &mailbox) override;
0089     Imap::Uids uidMapping(const QString &mailbox) const override;
0090 
0091     void clearAllMessages(const QString &mailbox) override;
0092     void clearMessage(const QString mailbox, const uint uid) override;
0093 
0094     MessageDataBundle messageMetadata(const QString &mailbox, uint uid) const override;
0095     void setMessageMetadata(const QString &mailbox, const uint uid, const MessageDataBundle &metadata) override;
0096 
0097     QStringList msgFlags(const QString &mailbox, const uint uid) const override;
0098     void setMsgFlags(const QString &mailbox, const uint uid, const QStringList &flags) override;
0099 
0100     QByteArray messagePart(const QString &mailbox, const uint uid, const QByteArray &partId) const override;
0101     void setMsgPart(const QString &mailbox, const uint uid, const QByteArray &partId, const QByteArray &data) override;
0102     void forgetMessagePart(const QString &mailbox, const uint uid, const QByteArray &partId) override;
0103 
0104     QVector<Imap::Responses::ThreadingNode> messageThreading(const QString &mailbox) override;
0105     void setMessageThreading(const QString &mailbox, const QVector<Imap::Responses::ThreadingNode> &threading) override;
0106 
0107     /** @short Open a connection to the cache */
0108     bool open(const QString &name, const QString &fileName);
0109 
0110     void setRenewalThreshold(const int days) override;
0111 
0112 private:
0113     /** @short Broadcast an error from the SQL query */
0114     void emitError(const QString &message, const QSqlQuery &query) const;
0115     /** @short Broadcast an error from the SQL "database" */
0116     void emitError(const QString &message, const QSqlDatabase &database) const;
0117     /** @short Broadcast a generic error */
0118     void emitError(const QString &message) const;
0119 
0120     /** @short Blindly create all tables */
0121     bool createTables();
0122     /** @short Initialize the prepared queries */
0123     bool prepareQueries();
0124 
0125     /** @short We're about to touch the DB, so it might be a good time to start a transaction */
0126     void touchingDB();
0127 
0128     /** @short Initialize the database */
0129     void init();
0130 
0131     static QString mailboxName(const QString &mailbox);
0132 
0133 private slots:
0134     /** @short We haven't committed for a while */
0135     void timeToCommit();
0136 
0137 private:
0138     // this needs to go before all QSqlDatabase/QSqlQuery instances for proper destruction order
0139     DbConnectionCleanup m_cleanup;
0140 
0141     QSqlDatabase db;
0142 
0143     mutable QSqlQuery queryChildMailboxes;
0144     mutable QSqlQuery queryChildMailboxesFresh;
0145     mutable QSqlQuery queryRemoveChildMailboxes;
0146     mutable QSqlQuery querySetChildMailboxes;
0147     mutable QSqlQuery queryMailboxSyncState;
0148     mutable QSqlQuery querySetMailboxSyncState;
0149     mutable QSqlQuery queryUidMapping;
0150     mutable QSqlQuery querySetUidMapping;
0151     mutable QSqlQuery queryClearUidMapping;
0152     mutable QSqlQuery queryMessageMetadata;
0153     mutable QSqlQuery queryAccessMessageMetadata;
0154     mutable QSqlQuery querySetMessageMetadata;
0155     mutable QSqlQuery queryMessageFlags;
0156     mutable QSqlQuery querySetMessageFlags;
0157     mutable QSqlQuery queryClearAllMessages1;
0158     mutable QSqlQuery queryClearAllMessages2;
0159     mutable QSqlQuery queryClearAllMessages3;
0160     mutable QSqlQuery queryClearAllMessages4;
0161     mutable QSqlQuery queryClearMessage1;
0162     mutable QSqlQuery queryClearMessage2;
0163     mutable QSqlQuery queryClearMessage3;
0164     mutable QSqlQuery queryMessagePart;
0165     mutable QSqlQuery querySetMessagePart;
0166     mutable QSqlQuery queryForgetMessagePart;
0167     mutable QSqlQuery queryMessageThreading;
0168     mutable QSqlQuery querySetMessageThreading;
0169 
0170     std::unique_ptr<QTimer> delayedCommit;
0171     std::unique_ptr<QTimer> tooMuchTimeWithoutCommit;
0172     bool inTransaction;
0173 
0174     /** @short A point in time against which the "last accessed on" data is computed */
0175     static QDate accessingThresholdDate;
0176 
0177     /** @short Update the "last accessed on" each time we are making an access *and* the difference is greater than X days
0178 
0179     To disable updating of the DB accesses, set to zero.
0180     */
0181     int m_updateAccessIfOlder;
0182 };
0183 
0184 }
0185 
0186 }
0187 
0188 #endif /* IMAP_MODEL_SQLCACHE_H */