Unity 8
AccountsServiceDBusAdaptor.cpp
1 /*
2  * Copyright (C) 2013 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  * Author: Michael Terry <michael.terry@canonical.com>
17  */
18 
19 #include "AccountsServiceDBusAdaptor.h"
20 #include <QDBusConnection>
21 #include <QDBusConnectionInterface>
22 #include <QDBusMessage>
23 #include <QDBusVariant>
24 #include <QDebug>
25 
26 AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
27  : QObject(parent),
28  m_accountsManager(nullptr),
29  m_ignoreNextChanged(false)
30 {
31  QDBusConnection connection = QDBusConnection::SM_BUSNAME();
32  QDBusConnectionInterface *interface = connection.interface();
33  interface->startService(QStringLiteral("org.freedesktop.Accounts"));
34  m_accountsManager = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
35  QStringLiteral("/org/freedesktop/Accounts"),
36  QStringLiteral("org.freedesktop.Accounts"),
37  connection, this);
38 }
39 
40 QVariant AccountsServiceDBusAdaptor::getUserProperty(const QString &user, const QString &interface, const QString &property)
41 {
42  QDBusInterface *iface = getUserInterface(user);
43  if (iface != nullptr && iface->isValid()) {
44  QDBusReply<QVariant> answer = iface->call(QStringLiteral("Get"), interface, property);
45  if (answer.isValid()) {
46  return answer;
47  }
48  }
49  return QVariant();
50 }
51 
52 QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
53 {
54  QDBusInterface *iface = getUserInterface(user);
55  if (iface != nullptr && iface->isValid()) {
56  return iface->asyncCall(QStringLiteral("Get"), interface, property);
57  }
58  return QDBusPendingReply<QVariant>(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
59 }
60 
61 void AccountsServiceDBusAdaptor::setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value)
62 {
63  QDBusInterface *iface = getUserInterface(user);
64  if (iface != nullptr && iface->isValid()) {
65  // The value needs to be carefully wrapped
66  iface->call(QStringLiteral("Set"), interface, property, QVariant::fromValue(QDBusVariant(value)));
67  }
68 }
69 
70 QDBusPendingCall AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value)
71 {
72  QDBusInterface *iface = getUserInterface(user);
73  if (iface != nullptr && iface->isValid()) {
74  // The value needs to be carefully wrapped
75  return iface->asyncCall(QStringLiteral("Set"), interface, property, QVariant::fromValue(QDBusVariant(value)));
76  }
77  return QDBusPendingCall::fromCompletedCall(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
78 }
79 
80 void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid)
81 {
82  // Merge changed and invalidated together
83  QStringList combined;
84  combined << invalid;
85  combined << changed.keys();
86  combined.removeDuplicates();
87 
88  Q_EMIT propertiesChanged(getUserForPath(message().path()), interface, combined);
89 
90  // In case a non-builtin property changes, we're getting propertiesChanged *and* changed
91  // As the generic changed requires asking back over DBus, it's quite slow to process.
92  // We don't want to trigger that when we know it's not a built-in property change.
93  m_ignoreNextChanged = true;
94 }
95 
96 void AccountsServiceDBusAdaptor::maybeChangedSlot()
97 {
98  if (!m_ignoreNextChanged) {
99  Q_EMIT maybeChanged(getUserForPath(message().path()));
100  }
101  m_ignoreNextChanged = false;
102 }
103 
104 QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path)
105 {
106  QMap<QString, QDBusInterface *>::const_iterator i;
107  for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
108  if (i.value()->path() == path) {
109  return i.key();
110  }
111  }
112  return QString();
113 }
114 
115 QDBusInterface *AccountsServiceDBusAdaptor::getUserInterface(const QString &user)
116 {
117  QDBusInterface *iface = m_users.value(user);
118  if (iface == nullptr && m_accountsManager->isValid()) {
119  QDBusReply<QDBusObjectPath> answer = m_accountsManager->asyncCall(QStringLiteral("FindUserByName"), user);
120  if (answer.isValid()) {
121  const QString path = answer.value().path();
122 
123  iface = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
124  path,
125  QStringLiteral("org.freedesktop.DBus.Properties"),
126  m_accountsManager->connection(), this);
127 
128  // With its own pre-defined properties, AccountsService is oddly
129  // close-lipped. It won't send out proper DBus.Properties notices,
130  // but it does have one catch-all Changed() signal. So let's
131  // listen to that.
132  iface->connection().connect(
133  iface->service(),
134  path,
135  QStringLiteral("org.freedesktop.Accounts.User"),
136  QStringLiteral("Changed"),
137  this,
138  SLOT(maybeChangedSlot()));
139 
140  // But custom properties do send out the right notifications, so
141  // let's still listen there.
142  iface->connection().connect(
143  iface->service(),
144  path,
145  QStringLiteral("org.freedesktop.DBus.Properties"),
146  QStringLiteral("PropertiesChanged"),
147  this,
148  SLOT(propertiesChangedSlot(QString, QVariantMap, QStringList)));
149 
150  m_users.insert(user, iface);
151  } else {
152  qWarning() << "Couldn't get user interface" << answer.error().name() << answer.error().message();
153  }
154  }
155  return iface;
156 }