aboutsummaryrefslogtreecommitdiff
path: root/libcontextsubscriber/src/subscriberinterface.cpp
blob: 9cac82b85568d184e1556f0e54a4070419a798eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
 * Copyright (C) 2008, 2009 Nokia Corporation.
 *
 * Contact: Marius Vollmer <marius.vollmer@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/*!
  \class SubscriberSignallingInterface

  \brief Proxy class for the DBus interface
  org.freedesktop.ContextKit.Subscriber which connects automatically
  to the Changed signal over DBus.

  It has to be a separate class because it needs the connection at
  initialization time, and we want to pass only the bus type which
  will be used to create the connection _after_ initialization time.
*/

/*!
  \class SubscriberInterface

  \brief Proxy class for using the DBus interface
  org.freedesktop.ContextKit.Subscriber asynchronously.

  Implements methods for constructing the interface objects (given the
  DBus type, session or system, and bus name), calling the functions
  Subscribe and Unsubscribe asynchronously, and listening to the
  Changed signal.
*/

#include "subscriberinterface.h"
#include "safedbuspendingcallwatcher.h"
#include "sconnect.h"
#include "logging.h"
#include "contextkitplugin.h"
#include <QDebug>
#include <QDBusConnection>
#include <QDBusPendingReply>

namespace ContextSubscriber {

const char *SubscriberInterface::interfaceName = "org.freedesktop.ContextKit.Subscriber";

/// Constructs the SubscriberInterface. Connects to the DBus object specified
/// by \a busType (session or system bus), \a busName and \a objectPath.
SubscriberInterface::SubscriberInterface(const QDBusConnection connection,
                                         const QString &busName,
                                         const QString &objectPath,
                                         QObject *parent)
    : QDBusAbstractInterface(busName, objectPath, interfaceName, connection, parent)
{
    sconnect(this, SIGNAL(Changed(const QMap<QString, QVariant>&, const QStringList &)),
             this, SLOT(onChanged(const QMap<QString, QVariant>&, const QStringList &)));
}

/// Calls the Subscribe function over DBus asynchronously.
void SubscriberInterface::subscribe(QSet<QString> keys)
{
    contextDebug() << "SubscriberInterface::subscribe";
    // FIXME contextDebug() << "SubscriberInterface::subscribe " << keys;
    if (isValid() == false || keys.size() == 0) {
        contextDebug() << "Subscriber cannot subscribe -> emitting subscribeFinished()";
        emit subscribeFinished(keys.toList());
        return;
    }

    // Construct the asynchronous call
    QStringList keyList = keys.toList();

    QDBusPendingCall subscribeCall = asyncCall("Subscribe", keyList);
    SafeDBusPendingCallWatcher *watcher = new SafeDBusPendingCallWatcher(subscribeCall, this);
    sconnect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
             this, SLOT(onSubscribeFinished(QDBusPendingCallWatcher *)));

    // When the pending call returns, we need to know the keys we
    // tried to subscribe to.
    watcher->setProperty("keysToSubscribe", keyList);
}

/// Calls the Unsubscribe function over DBus asynchronously.
void SubscriberInterface::unsubscribe(QSet<QString> keys)
{
    if (isValid() && keys.size() != 0) {
        // Construct the asynchronous call
        QStringList keyList = keys.toList();

        asyncCall("Unsubscribe", keyList);
        // The possible errors are not tracked, because we can't do anything if Unsubscribe fails.
    }
}

/// Processes the results of the Changed signal which comes over DBus.
void SubscriberInterface::onChanged(const QMap<QString, QVariant> &values, const QStringList& unknownKeys)
{
    QMapIterator<QString, QVariant> it(values);
    QMap<QString, QVariant> copy;
    while (it.hasNext()) {
        it.next();
        copy.insert(it.key(), demarshallValue(it.value()));
    }
    emit valuesChanged(mergeNullsWithMap(copy, unknownKeys));
}

/// A helper function. Sets the values of given keys to a null QVariant in a QMap.
QMap<QString, QVariant>& SubscriberInterface::mergeNullsWithMap(QMap<QString, QVariant> &map, QStringList nulls) const
{
    foreach (QString null, nulls) {
        if (map.contains(null))
            contextWarning() << "Provider error, provided unknown and a value for" << null;
        else
            map[null] = QVariant();
    }
    return map;
}

/// Is called when the asynchronous DBus call to Subscribe has finished. Emits
/// the signal valuesChanged with the return values of the subscribed keys.
void SubscriberInterface::onSubscribeFinished(QDBusPendingCallWatcher* watcher)
{
    QDBusPendingReply<QMap<QString, QVariant>, QStringList> reply = *watcher;

    QList<QString> requestedKeys = watcher->property("keysToSubscribe").toStringList();

    if (reply.isError()) {
        // Possible causes of the error:
        // The provider is not running
        // The provider didn't implement the needed interface + function
        // The function resulted in an error
        contextWarning() << "Provider error while subscribing:" << reply.error().message();
        emit subscribeFailed(requestedKeys, "Provider error");
    } else {
        QMap<QString, QVariant> subscribeTimeValues = reply.argumentAt<0>();
        QStringList unknowns = reply.argumentAt<1>();

        // TODO: the protocol should be better, this is just a workaround
        QMap<QString, QVariant> okMap = mergeNullsWithMap(subscribeTimeValues, unknowns);
        emit subscribeFinished(okMap.keys());
        emit valuesChanged(okMap);
        emit subscribeFailed((requestedKeys.toSet() - okMap.keys().toSet()).toList(), "Not provided");
    }
}

void SubscriberInterface::connectNotify(const char *signal)
{
    // only Changed signal should be AddMatch'd on the DBus side
    if (qstrcmp(signal, SIGNAL(Changed(QMap<QString,QVariant>,QStringList))) == 0)
        QDBusAbstractInterface::connectNotify(signal);
    else
        QObject::connectNotify(signal);
}

void SubscriberInterface::disconnectNotify(const char *signal)
{
    // only Changed signal should be AddMatch'd on the DBus side
    if (qstrcmp(signal, SIGNAL(Changed(QMap<QString,QVariant>,QStringList))) == 0)
        QDBusAbstractInterface::disconnectNotify(signal);
    else
        QObject::disconnectNotify(signal);
}

} // end namespace