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