Unity 8
AccountsServiceDBusAdaptor.cpp
1 /*
2  * Copyright (C) 2013-2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "AccountsServiceDBusAdaptor.h"
18 #include <QDBusConnection>
19 #include <QDBusConnectionInterface>
20 #include <QDBusMessage>
21 #include <QDBusVariant>
22 #include <QDebug>
23 
24 AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
25  : QObject(parent),
26  m_accountsManager(nullptr),
27  m_ignoreNextChanged(false)
28 {
29  QDBusConnection connection = QDBusConnection::SM_BUSNAME();
30  QDBusConnectionInterface *interface = connection.interface();
31  interface->startService(QStringLiteral("org.freedesktop.Accounts"));
32  m_accountsManager = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
33  QStringLiteral("/org/freedesktop/Accounts"),
34  QStringLiteral("org.freedesktop.Accounts"),
35  connection, this);
36 }
37 
38 QDBusPendingReply<QVariantMap> AccountsServiceDBusAdaptor::getAllPropertiesAsync(const QString &user, const QString &interface)
39 {
40  QDBusInterface *iface = getUserInterface(user);
41  if (iface != nullptr && iface->isValid()) {
42  return iface->asyncCall(QStringLiteral("GetAll"), interface);
43  }
44  return QDBusPendingReply<QVariantMap>(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
45 }
46 
47 QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
48 {
49  QDBusInterface *iface = getUserInterface(user);
50  if (iface != nullptr && iface->isValid()) {
51  return iface->asyncCall(QStringLiteral("Get"), interface, property);
52  }
53  return QDBusPendingReply<QVariant>(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
54 }
55 
56 QDBusPendingCall AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value)
57 {
58  QDBusInterface *iface = getUserInterface(user);
59  if (iface != nullptr && iface->isValid()) {
60  // The value needs to be carefully wrapped
61  return iface->asyncCall(QStringLiteral("Set"), interface, property, QVariant::fromValue(QDBusVariant(value)));
62  }
63  return QDBusPendingCall::fromCompletedCall(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
64 }
65 
66 void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid)
67 {
68  // Merge changed and invalidated together
69  QStringList combined;
70  combined << invalid;
71  combined << changed.keys();
72  combined.removeDuplicates();
73 
74  Q_EMIT propertiesChanged(getUserForPath(message().path()), interface, combined);
75 
76  // In case a non-builtin property changes, we're getting propertiesChanged *and* changed
77  // As the generic changed requires asking back over DBus, it's quite slow to process.
78  // We don't want to trigger that when we know it's not a built-in property change.
79  m_ignoreNextChanged = true;
80 }
81 
82 void AccountsServiceDBusAdaptor::maybeChangedSlot()
83 {
84  if (!m_ignoreNextChanged) {
85  Q_EMIT maybeChanged(getUserForPath(message().path()));
86  }
87  m_ignoreNextChanged = false;
88 }
89 
90 QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path) const
91 {
92  QMap<QString, QDBusInterface *>::const_iterator i;
93  for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
94  if (i.value()->path() == path) {
95  return i.key();
96  }
97  }
98  return QString();
99 }
100 
101 QDBusInterface *AccountsServiceDBusAdaptor::getUserInterface(const QString &user)
102 {
103  QDBusInterface *iface = m_users.value(user);
104  if (iface == nullptr && m_accountsManager->isValid()) {
105  QDBusReply<QDBusObjectPath> answer = m_accountsManager->call(QStringLiteral("FindUserByName"), user);
106  if (answer.isValid()) {
107  const QString path = answer.value().path();
108 
109  iface = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
110  path,
111  QStringLiteral("org.freedesktop.DBus.Properties"),
112  m_accountsManager->connection(), this);
113 
114  // With its own pre-defined properties, AccountsService is oddly
115  // close-lipped. It won't send out proper DBus.Properties notices,
116  // but it does have one catch-all Changed() signal. So let's
117  // listen to that.
118  iface->connection().connect(
119  iface->service(),
120  path,
121  QStringLiteral("org.freedesktop.Accounts.User"),
122  QStringLiteral("Changed"),
123  this,
124  SLOT(maybeChangedSlot()));
125 
126  // But custom properties do send out the right notifications, so
127  // let's still listen there.
128  iface->connection().connect(
129  iface->service(),
130  path,
131  QStringLiteral("org.freedesktop.DBus.Properties"),
132  QStringLiteral("PropertiesChanged"),
133  this,
134  SLOT(propertiesChangedSlot(QString, QVariantMap, QStringList)));
135 
136  m_users.insert(user, iface);
137  } else {
138  qWarning() << "Couldn't get user interface" << answer.error().name() << answer.error().message();
139  }
140  }
141  return iface;
142 }