KPeople

personmanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2013 David Edmundson <[email protected]>
3  SPDX-FileCopyrightText: 2013 Martin Klapetek <[email protected]>
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 
22 class Transaction
23 {
24 public:
25  Transaction(const QSqlDatabase &db);
26  void cancel();
27  ~Transaction();
28  Transaction(const Transaction &) = delete;
29  Transaction &operator=(const Transaction &) = delete;
30 
31 private:
32  QSqlDatabase m_db;
33  bool m_cancelled = false;
34 };
35 
36 Transaction::Transaction(const QSqlDatabase &db)
37  : m_db(db)
38 {
39  m_db.transaction();
40 }
41 
42 void Transaction::cancel()
43 {
44  m_db.rollback();
45  m_cancelled = true;
46 }
47 
48 Transaction::~Transaction()
49 {
50  if (!m_cancelled) {
51  m_db.commit();
52  }
53 }
54 
55 PersonManager::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 
83 PersonManager::~PersonManager()
84 {
85 }
86 
87 QMultiHash<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 
100 QStringList 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 
119 QString 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 
131 QString 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
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
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 
250 bool 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
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 
284 PersonManager *PersonManager::instance(const QString &databasePath)
285 {
286  static PersonManager *s_instance = nullptr;
287  if (!s_instance) {
288  QString path = databasePath;
289  if (path.isEmpty()) {
290  path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kpeople/");
291 
292  QDir().mkpath(path);
293  path += QLatin1String("persondb");
294  }
295  s_instance = new PersonManager(path);
296  }
297  return s_instance;
298 }
T & first()
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QString number(int n, int base)
int count(const T &value) const const
void clear()
QString writableLocation(QStandardPaths::StandardLocation type)
bool send(const QDBusMessage &message) const const
KGuiItem cancel()
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
int size() const const
QDBusConnection sessionBus()
bool isEmpty() const const
bool mkpath(const QString &dirPath) const const
bool isEmpty() const const
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString mid(int position, int n) const const
QString message
T value(int i) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Aug 14 2022 04:09:08 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.