KPeople

personmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2013 David Edmundson <davidedmundson@kde.org>
3 SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "personmanager_p.h"
9
10#include "kpeople_debug.h"
11
12#include <QDir>
13#include <QSqlQuery>
14#include <QStandardPaths>
15#include <QVariant>
16
17#ifdef QT_DBUS_LIB
18#include <QDBusConnection>
19#include <QDBusMessage>
20#endif
21
22class Transaction
23{
24public:
25 Transaction(const QSqlDatabase &db);
26 void cancel();
27 ~Transaction();
28 Transaction(const Transaction &) = delete;
29 Transaction &operator=(const Transaction &) = delete;
30
31private:
32 QSqlDatabase m_db;
33 bool m_cancelled = false;
34};
35
36Transaction::Transaction(const QSqlDatabase &db)
37 : m_db(db)
38{
39 m_db.transaction();
40}
41
42void Transaction::cancel()
43{
44 m_db.rollback();
45 m_cancelled = true;
46}
47
48Transaction::~Transaction()
49{
50 if (!m_cancelled) {
51 m_db.commit();
52 }
53}
54
55PersonManager::PersonManager(const QString &databasePath, QObject *parent)
56 : QObject(parent)
57 , m_db(QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("kpeoplePersonsManager")))
58{
59 m_db.setDatabaseName(databasePath);
60 if (!m_db.open()) {
61 qCWarning(KPEOPLE_LOG) << "Couldn't open the database at" << databasePath;
62 }
63 m_db.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS persons (contactID VARCHAR UNIQUE NOT NULL, personID INT NOT NULL)"));
64 m_db.exec(QStringLiteral("CREATE INDEX IF NOT EXISTS contactIdIndex ON persons (contactId)"));
65 m_db.exec(QStringLiteral("CREATE INDEX IF NOT EXISTS personIdIndex ON persons (personId)"));
66
67#ifdef QT_DBUS_LIB
69 QStringLiteral("/KPeople"),
70 QStringLiteral("org.kde.KPeople"),
71 QStringLiteral("ContactAddedToPerson"),
72 this,
73 SIGNAL(contactAddedToPerson(QString, QString)));
75 QStringLiteral("/KPeople"),
76 QStringLiteral("org.kde.KPeople"),
77 QStringLiteral("ContactRemovedFromPerson"),
78 this,
79 SIGNAL(contactRemovedFromPerson(QString)));
80#endif
81}
82
83PersonManager::~PersonManager()
84{
85}
86
87QMultiHash<QString, QString> PersonManager::allPersons() const
88{
89 QMultiHash<QString /*PersonID*/, QString /*ContactID*/> contactMapping;
90
91 QSqlQuery query = m_db.exec(QStringLiteral("SELECT personID, contactID FROM persons"));
92 while (query.next()) {
93 const QString personUri = QLatin1String("kpeople://") + query.value(0).toString(); // we store as ints internally, convert it to a string here
94 const QString contactID = query.value(1).toString();
95 contactMapping.insert(personUri, contactID);
96 }
97 return contactMapping;
98}
99
100QStringList PersonManager::contactsForPersonUri(const QString &personUri) const
101{
102 if (!personUri.startsWith(QLatin1String("kpeople://"))) {
103 return QStringList();
104 }
105
106 QStringList contactUris;
107 // TODO port to the proper qsql method for args
108 QSqlQuery query(m_db);
109 query.prepare(QStringLiteral("SELECT contactID FROM persons WHERE personId = ?"));
110 query.bindValue(0, personUri.mid(strlen("kpeople://")));
111 query.exec();
112
113 while (query.next()) {
114 contactUris << query.value(0).toString();
115 }
116 return contactUris;
117}
118
119QString PersonManager::personUriForContact(const QString &contactUri) const
120{
121 QSqlQuery query(m_db);
122 query.prepare(QStringLiteral("SELECT personId FROM persons WHERE contactId = ?"));
123 query.bindValue(0, contactUri);
124 query.exec();
125 if (query.next()) {
126 return QLatin1String("kpeople://") + query.value(0).toString();
127 }
128 return QString();
129}
130
131QString PersonManager::mergeContacts(const QStringList &ids)
132{
133 // no merging if we have only 0 || 1 ids
134 if (ids.size() < 2) {
135 return QString();
136 }
137
138 QStringList metacontacts;
139 QStringList contacts;
140
141 bool rc = true;
142
143#ifdef QT_DBUS_LIB
144 QList<QDBusMessage> pendingMessages;
145#endif
146
147 // separate the passed ids to metacontacts and simple contacts
148 for (const QString &id : ids) {
149 if (id.startsWith(QLatin1String("kpeople://"))) {
150 metacontacts << id;
151 } else {
152 contacts << id;
153 }
154 }
155
156 // create new personUriString
157 // - if we're merging two simple contacts, create completely new id
158 // - if we're merging an existing metacontact, take the first id and use it
159 QString personUriString;
160 if (metacontacts.isEmpty()) {
161 // query for the highest existing ID in the database and +1 it
162 int personUri = 0;
163 QSqlQuery query = m_db.exec(QStringLiteral("SELECT MAX(personID) FROM persons"));
164 if (query.next()) {
165 personUri = query.value(0).toInt();
166 personUri++;
167 }
168
169 personUriString = QLatin1String("kpeople://") + QString::number(personUri);
170 } else {
171 personUriString = metacontacts.first();
172 }
173
174 // start a db transaction (ends automatically on destruction)
175 Transaction t(m_db);
176
177 // processed passed metacontacts
178 if (metacontacts.count() > 1) {
179 // collect all the contacts from other persons
180 QStringList personContacts;
181 for (const QString &id : std::as_const(metacontacts)) {
182 if (id != personUriString) {
183 personContacts << contactsForPersonUri(id);
184 }
185 }
186
187 // iterate over all of the contacts and change their personID to the new personUriString
188 for (const QString &id : std::as_const(personContacts)) {
189 QSqlQuery updateQuery(m_db);
190 updateQuery.prepare(QStringLiteral("UPDATE persons SET personID = ? WHERE contactID = ?"));
191 updateQuery.bindValue(0, personUriString.mid(strlen("kpeople://")));
192 updateQuery.bindValue(1, id);
193 if (!updateQuery.exec()) {
194 rc = false;
195 }
196
197#ifdef QT_DBUS_LIB
198 QDBusMessage message =
199 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactRemovedFromPerson"));
200
201 message.setArguments(QVariantList() << id);
202 pendingMessages << message;
203
204 message = QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactAddedToPerson"));
205
206 message.setArguments(QVariantList() << id << personUriString);
207 pendingMessages << message;
208#endif
209 }
210 }
211
212 // process passed contacts
213 if (!contacts.isEmpty()) {
214 for (const QString &id : std::as_const(contacts)) {
215 QSqlQuery insertQuery(m_db);
216 insertQuery.prepare(QStringLiteral("INSERT INTO persons VALUES (?, ?)"));
217 insertQuery.bindValue(0, id);
218 insertQuery.bindValue(1, personUriString.mid(strlen("kpeople://"))); // strip kpeople://
219 if (!insertQuery.exec()) {
220 rc = false;
221 }
222
223#ifdef QT_DBUS_LIB
224 // FUTURE OPTIMIZATION - this would be best as one signal, but arguments become complex
225 QDBusMessage message =
226 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactAddedToPerson"));
227
228 message.setArguments(QVariantList() << id << personUriString);
229 pendingMessages << message;
230#endif
231 }
232 }
233
234 // if success send all messages to other clients
235 // otherwise roll back our database changes and return an empty string
236 if (rc) {
237#ifdef QT_DBUS_LIB
238 for (const QDBusMessage &message : std::as_const(pendingMessages)) {
240 }
241#endif
242 } else {
243 t.cancel();
244 personUriString.clear();
245 }
246
247 return personUriString;
248}
249
250bool PersonManager::unmergeContact(const QString &id)
251{
252 // remove rows from DB
253 if (id.startsWith(QLatin1String("kpeople://"))) {
254 QSqlQuery query(m_db);
255
256 const QStringList contactUris = contactsForPersonUri(id);
257 query.prepare(QStringLiteral("DELETE FROM persons WHERE personId = ?"));
258 query.bindValue(0, id.mid(strlen("kpeople://")));
259 query.exec();
260
261#ifdef QT_DBUS_LIB
262 for (const QString &contactUri : contactUris) {
263 // FUTURE OPTIMIZATION - this would be best as one signal, but arguments become complex
264 QDBusMessage message =
265 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactRemovedFromPerson"));
266
267 message.setArguments(QVariantList() << contactUri);
269 }
270#endif
271 } else {
272 QSqlQuery query(m_db);
273 query.prepare(QStringLiteral("DELETE FROM persons WHERE contactId = ?"));
274 query.bindValue(0, id);
275 query.exec();
276 // emit signal(dbus)
277 Q_EMIT contactRemovedFromPerson(id);
278 }
279
280 // TODO return if removing rows worked
281 return true;
282}
283
284PersonManager *PersonManager::instance(const QString &databasePath)
285{
286 static PersonManager *s_instance = nullptr;
287 if (!s_instance) {
288 QString path = databasePath;
289 if (path.isEmpty()) {
291
292 QDir().mkpath(path);
293 path += QLatin1String("persondb");
294 }
295 s_instance = new PersonManager(path);
296 }
297 return s_instance;
298}
299
300#include "moc_personmanager_p.cpp"
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QString path(const QString &relativePath)
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
void setArguments(const QList< QVariant > &arguments)
bool mkpath(const QString &dirPath) const const
qsizetype count() const const
T & first()
bool isEmpty() const const
qsizetype size() const const
T value(qsizetype i) const const
bool rollback()
QString writableLocation(StandardLocation type)
void clear()
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:45 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.