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