1 /***************************************************************************
2  *   SPDX-FileCopyrightText: 2006 Andreas Gungl <a.gungl@gmx.de>           *
3  *                                                                         *
4  *   SPDX-License-Identifier: LGPL-2.0-or-later                            *
5  ***************************************************************************/
6 
7 #pragma once
8 
9 #include <QList>
10 #include <QMutex>
11 #include <QObject>
12 #include <QSqlDatabase>
13 #include <QThreadStorage>
14 #include <QVector>
15 
16 class QSqlQuery;
17 class QTimer;
18 
19 #include "entities.h"
20 #include "notificationcollector.h"
21 
22 #include <memory>
23 
24 namespace Akonadi
25 {
26 namespace Server
27 {
28 class DataStore;
29 class DataStoreFactory
30 {
31 public:
32     virtual ~DataStoreFactory() = default;
33     virtual DataStore *createStore() = 0;
34 
35 protected:
36     explicit DataStoreFactory() = default;
37 
38 private:
39     Q_DISABLE_COPY_MOVE(DataStoreFactory)
40 };
41 
42 class NotificationCollector;
43 
44 /**
45   This class handles all the database access.
46 
47   <h4>Database configuration</h4>
48 
49   You can select between various database backends during runtime using the
50   @c $HOME/.config/akonadi/akonadiserverrc configuration file.
51 
52   Example:
53 @verbatim
54 [%General]
55 Driver=QMYSQL
56 
57 [QMYSQL_EMBEDDED]
58 Name=akonadi
59 Options=SERVER_DATADIR=/home/foo/.local/share/akonadi/db_data
60 
61 [QMYSQL]
62 Name=akonadi
63 Host=localhost
64 User=foo
65 Password=*****
66 #Options=UNIX_SOCKET=/home/foo/.local/share/akonadi/socket-bar/mysql.socket
67 StartServer=true
68 ServerPath=/usr/sbin/mysqld
69 
70 [QSQLITE]
71 Name=/home/foo/.local/share/akonadi/akonadi.db
72 @endverbatim
73 
74   Use @c General/Driver to select the QSql driver to use for database
75   access. The following drivers are currently supported, other might work
76   but are untested:
77 
78   - QMYSQL
79   - QMYSQL_EMBEDDED
80   - QSQLITE
81 
82   The options for each driver are read from the corresponding group.
83   The following options are supported, dependent on the driver not all of them
84   might have an effect:
85 
86   - Name: Database name, for sqlite that's the file name of the database.
87   - Host: Hostname of the database server
88   - User: Username for the database server
89   - Password: Password for the database server
90   - Options: Additional options, format is driver-dependent
91   - StartServer: Start the database locally just for Akonadi instead of using an existing one
92   - ServerPath: Path to the server executable
93 */
94 class DataStore : public QObject
95 {
96     Q_OBJECT
97 public:
98     const constexpr static bool Silent = true;
99 
100     static void setFactory(std::unique_ptr<DataStoreFactory> factory);
101 
102     /**
103       Closes the database connection and destroys the DataStore object.
104     */
105     ~DataStore() override;
106 
107     /**
108       Opens the database connection.
109     */
110     virtual void open();
111 
112     /**
113       Closes the database connection.
114     */
115     void close();
116 
117     /**
118       Initializes the database. Should be called during startup by the main thread.
119     */
120     virtual bool init();
121 
122     /**
123       Per thread singleton.
124     */
125     static DataStore *self();
126 
127     /**
128      * Returns whether per thread DataStore has been created.
129      */
130     static bool hasDataStore();
131 
132     /* --- ItemFlags ----------------------------------------------------- */
133     virtual bool setItemsFlags(const PimItem::List &items,
134                                const QVector<Flag> *currentFlags,
135                                const QVector<Flag> &newFlags,
136                                bool *flagsChanged = nullptr,
137                                const Collection &col = Collection(),
138                                bool silent = false);
139     virtual bool appendItemsFlags(const PimItem::List &items,
140                                   const QVector<Flag> &flags,
141                                   bool *flagsChanged = nullptr,
142                                   bool checkIfExists = true,
143                                   const Collection &col = Collection(),
144                                   bool silent = false);
145     virtual bool removeItemsFlags(const PimItem::List &items,
146                                   const QVector<Flag> &flags,
147                                   bool *tagsChanged = nullptr,
148                                   const Collection &collection = Collection(),
149                                   bool silent = false);
150 
151     /* --- ItemTags ----------------------------------------------------- */
152     virtual bool setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false);
153     virtual bool appendItemsTags(const PimItem::List &items,
154                                  const Tag::List &tags,
155                                  bool *tagsChanged = nullptr,
156                                  bool checkIfExists = true,
157                                  const Collection &col = Collection(),
158                                  bool silent = false);
159     virtual bool removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false);
160     virtual bool removeTags(const Tag::List &tags, bool silent = false);
161 
162     /* --- ItemParts ----------------------------------------------------- */
163     virtual bool removeItemParts(const PimItem &item, const QSet<QByteArray> &parts);
164 
165     // removes all payload parts for this item.
166     virtual bool invalidateItemCache(const PimItem &item);
167 
168     /* --- Collection ------------------------------------------------------ */
169     virtual bool appendCollection(Collection &collection, const QStringList &mimeTypes, const QMap<QByteArray, QByteArray> &attributes);
170 
171     /// removes the given collection and all its content
172     virtual bool cleanupCollection(Collection &collection);
173     /// same as the above but for database backends without working referential actions on foreign keys
174     virtual bool cleanupCollection_slow(Collection &collection);
175 
176     /// moves the collection @p collection to @p newParent.
177     virtual bool moveCollection(Collection &collection, const Collection &newParent);
178 
179     virtual bool appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes);
180 
collectionDelimiter()181     static QString collectionDelimiter()
182     {
183         return QStringLiteral("/");
184     }
185 
186     /**
187       Determines the active cache policy for this Collection.
188       The active cache policy is set in the corresponding Collection fields.
189     */
190     virtual void activeCachePolicy(Collection &col);
191 
192     /// Returns all virtual collections the @p item is linked to
193     QVector<Collection> virtualCollections(const PimItem &item);
194 
195     QMap<Server::Entity::Id, QList<PimItem>> virtualCollections(const Akonadi::Server::PimItem::List &items);
196 
197     /* --- PimItem ------------------------------------------------------- */
198     virtual bool appendPimItem(QVector<Part> &parts,
199                                const QVector<Flag> &flags,
200                                const MimeType &mimetype,
201                                const Collection &collection,
202                                const QDateTime &dateTime,
203                                const QString &remote_id,
204                                const QString &remoteRevision,
205                                const QString &gid,
206                                PimItem &pimItem);
207     /**
208      * Removes the pim item and all referenced data ( e.g. flags )
209      */
210     virtual bool cleanupPimItems(const PimItem::List &items, bool silent = false);
211 
212     /**
213      * Unhides the specified PimItem. Emits the itemAdded() notification as
214      * the hidden flag is assumed to have been set by appendPimItem() before
215      * pushing the item to the preprocessor chain. The hidden item had his
216      * notifications disabled until now (so for the clients the "unhide" operation
217      * is actually a new item arrival).
218      *
219      * This function does NOT verify if the item was *really* hidden: this is
220      * responsibility of the caller.
221      */
222     virtual bool unhidePimItem(PimItem &pimItem);
223 
224     /**
225      * Unhides all the items which have the "hidden" flag set.
226      * This function doesn't emit any notification about the items
227      * being unhidden so it's meant to be called only in rare circumstances.
228      * The most notable call to this function is at server startup
229      * when we attempt to restore a clean state of the database.
230      */
231     virtual bool unhideAllPimItems();
232 
233     /* --- Collection attributes ------------------------------------------ */
234     virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value, bool silent = false);
235     /**
236      * Removes the given collection attribute for @p col.
237      * @throws HandlerException on database errors
238      * @returns @c true if the attribute existed, @c false otherwise
239      */
240     virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key);
241 
242     /* --- Helper functions ---------------------------------------------- */
243 
244     /**
245       Begins a transaction. No changes will be written to the database and
246       no notification signal will be emitted unless you call commitTransaction().
247       @return @c true if successful.
248     */
249     virtual bool beginTransaction(const QString &name);
250 
251     /**
252       Reverts all changes within the current transaction.
253     */
254     virtual bool rollbackTransaction();
255 
256     /**
257       Commits all changes within the current transaction and emits all
258       collected notification signals. If committing fails, the transaction
259       will be rolled back.
260     */
261     virtual bool commitTransaction();
262 
263     /**
264       Returns true if there is a transaction in progress.
265     */
266     bool inTransaction() const;
267 
268     /**
269       Returns the notification collector of this DataStore object.
270       Use this to listen to change notification signals.
271     */
272     NotificationCollector *notificationCollector();
273 
274     /**
275       Returns the QSqlDatabase object. Use this for generating queries yourself.
276 
277       Will [re-]open the database, if it is closed.
278     */
279     QSqlDatabase database();
280 
281     /**
282       Sets the current session id.
283     */
setSessionId(const QByteArray & sessionId)284     void setSessionId(const QByteArray &sessionId)
285     {
286         mSessionId = sessionId;
287     }
288 
289     /**
290       Returns if the database is currently open
291       */
isOpened()292     bool isOpened() const
293     {
294         return m_dbOpened;
295     }
296 
297     bool doRollback();
298     void transactionKilledByDB();
299 
300 Q_SIGNALS:
301     /**
302       Emitted if a transaction has been successfully committed.
303     */
304     void transactionCommitted();
305     /**
306       Emitted if a transaction has been aborted.
307     */
308     void transactionRolledBack();
309 
310 protected:
311     /**
312       Creates a new DataStore object and opens it.
313     */
314     DataStore(AkonadiServer &akonadi);
315 
316     void debugLastDbError(const char *actionDescription) const;
317     void debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const;
318 
319 private:
320     bool doAppendItemsFlag(const PimItem::List &items, const Flag &flag, const QSet<PimItem::Id> &existing, const Collection &col, bool silent);
321 
322     bool doAppendItemsTag(const PimItem::List &items, const Tag &tag, const QSet<Entity::Id> &existing, const Collection &col, bool silent);
323 
324     /** Converts the given date/time to the database format, i.e.
325         "YYYY-MM-DD HH:MM:SS".
326         @param dateTime the date/time in UTC
327         @return the date/time in database format
328         @see dateTimeToQDateTime
329      */
330     static QString dateTimeFromQDateTime(const QDateTime &dateTime);
331 
332     /** Converts the given date/time from database format to QDateTime.
333         @param dateTime the date/time in database format
334         @return the date/time as QDateTime
335         @see dateTimeFromQDateTime
336      */
337     static QDateTime dateTimeToQDateTime(const QByteArray &dateTime);
338 
339 private Q_SLOTS:
340     void sendKeepAliveQuery();
341 
342 protected:
343     static std::unique_ptr<DataStoreFactory> sFactory;
344     std::unique_ptr<NotificationCollector> mNotificationCollector;
345     AkonadiServer &m_akonadi;
346 
347 private:
348     Q_DISABLE_COPY_MOVE(DataStore)
349 
350     void cleanupAfterRollback();
351     QString m_connectionName;
352     QSqlDatabase m_database;
353     bool m_dbOpened;
354     bool m_transactionKilledByDB = false;
355     uint m_transactionLevel;
356     struct TransactionQuery {
357         QString query;
358         QVector<QVariant> boundValues;
359         bool isBatch;
360     };
361     QByteArray mSessionId;
362     QTimer *m_keepAliveTimer = nullptr;
363     static bool s_hasForeignKeyConstraints;
364     static QMutex sTransactionMutex;
365 
366     friend class DataStoreFactory;
367 };
368 
369 } // namespace Server
370 } // namespace Akonadi
371 
372