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#if HAVE_QTDBUS
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#if HAVE_QTDBUS
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 QStringList pendingContactRemovedFromPerson;
144 QVector<QPair<QString, QString>> pendingContactAddedToPerson;
145
146 // separate the passed ids to metacontacts and simple contacts
147 for (const QString &id : ids) {
148 if (id.startsWith(QLatin1String("kpeople://"))) {
149 metacontacts << id;
150 } else {
151 contacts << id;
152 }
153 }
154
155 // create new personUriString
156 // - if we're merging two simple contacts, create completely new id
157 // - if we're merging an existing metacontact, take the first id and use it
158 QString personUriString;
159 if (metacontacts.isEmpty()) {
160 // query for the highest existing ID in the database and +1 it
161 int personUri = 0;
162 QSqlQuery query = m_db.exec(QStringLiteral("SELECT MAX(personID) FROM persons"));
163 if (query.next()) {
164 personUri = query.value(0).toInt();
165 personUri++;
166 }
167
168 personUriString = QLatin1String("kpeople://") + QString::number(personUri);
169 } else {
170 personUriString = metacontacts.first();
171 }
172
173 // start a db transaction (ends automatically on destruction)
174 Transaction t(m_db);
175
176 // processed passed metacontacts
177 if (metacontacts.count() > 1) {
178 // collect all the contacts from other persons
179 QStringList personContacts;
180 for (const QString &id : std::as_const(metacontacts)) {
181 if (id != personUriString) {
182 personContacts << contactsForPersonUri(id);
183 }
184 }
185
186 // iterate over all of the contacts and change their personID to the new personUriString
187 for (const QString &id : std::as_const(personContacts)) {
188 QSqlQuery updateQuery(m_db);
189 updateQuery.prepare(QStringLiteral("UPDATE persons SET personID = ? WHERE contactID = ?"));
190 updateQuery.bindValue(0, personUriString.mid(strlen("kpeople://")));
191 updateQuery.bindValue(1, id);
192 if (!updateQuery.exec()) {
193 rc = false;
194 }
195
196 pendingContactRemovedFromPerson.append(id);
197 pendingContactAddedToPerson.append({id, personUriString});
198 }
199 }
200
201 // process passed contacts
202 if (!contacts.isEmpty()) {
203 for (const QString &id : std::as_const(contacts)) {
204 QSqlQuery insertQuery(m_db);
205 insertQuery.prepare(QStringLiteral("INSERT INTO persons VALUES (?, ?)"));
206 insertQuery.bindValue(0, id);
207 insertQuery.bindValue(1, personUriString.mid(strlen("kpeople://"))); // strip kpeople://
208 if (!insertQuery.exec()) {
209 rc = false;
210 }
211
212 // FUTURE OPTIMIZATION - this would be best as one signal, but arguments become complex
213 pendingContactAddedToPerson.append({id, personUriString});
214 }
215 }
216
217 // if success send all messages to other clients
218 // otherwise roll back our database changes and return an empty string
219 if (rc) {
220 for (const auto &id : std::as_const(pendingContactRemovedFromPerson)) {
221#if HAVE_QTDBUS
222 QDBusMessage message =
223 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactRemovedFromPerson"));
224 message.setArguments(QVariantList() << id);
226#else
227 Q_EMIT contactRemovedFromPerson(id);
228#endif
229 }
230 for (const auto &it : std::as_const(pendingContactAddedToPerson)) {
231#if HAVE_QTDBUS
232 QDBusMessage message =
233 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactAddedToPerson"));
234 message.setArguments(QVariantList() << it.first << it.second);
236#else
237 Q_EMIT contactAddedToPerson(it.first, it.second);
238#endif
239 }
240 } else {
241 t.cancel();
242 personUriString.clear();
243 }
244
245 return personUriString;
246}
247
248bool PersonManager::unmergeContact(const QString &id)
249{
250 // remove rows from DB
251 if (id.startsWith(QLatin1String("kpeople://"))) {
252 QSqlQuery query(m_db);
253
254 const QStringList contactUris = contactsForPersonUri(id);
255 query.prepare(QStringLiteral("DELETE FROM persons WHERE personId = ?"));
256 query.bindValue(0, id.mid(strlen("kpeople://")));
257 query.exec();
258
259 for (const QString &contactUri : contactUris) {
260#if HAVE_QTDBUS
261 // FUTURE OPTIMIZATION - this would be best as one signal, but arguments become complex
262 QDBusMessage message =
263 QDBusMessage::createSignal(QStringLiteral("/KPeople"), QStringLiteral("org.kde.KPeople"), QStringLiteral("ContactRemovedFromPerson"));
264
265 message.setArguments(QVariantList() << contactUri);
267#else
268 Q_EMIT contactRemovedFromPerson(contactUri);
269#endif
270 }
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"
std::optional< QSqlQuery > query(const QString &queryStatement)
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
void append(QList< T > &&value)
qsizetype count() const const
T & first()
bool isEmpty() const const
qsizetype size() 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 Sat Dec 21 2024 17:03:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.