aboutsummaryrefslogtreecommitdiff
path: root/libcontextsubscriber/src/contextproperty.cpp
blob: 3a8f33c3b43f3796063c2d45cdc6ad5ca698fd94 (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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*
 * 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
 *
 */

/*!
    \mainpage Context Properties

    \brief The Context Framework allows you to access system- and
    session-wide named values. Examples are context properties like the
    current geographical location. You can receive notifications about
    changes to these values, and you can also easily subscribe and
    unsubscribe from change notifications to help with managing power
    consumption.

    \section Overview

    The Context Properties are key/value pairs. The keys are
    strings and the values are QVariants.

    Key are arranged in a hierarchical namespace like in this example
    of two contextual properties

    \code
    Screen.TopEdge = "left"
    Screen.IsCovered = false
    \endcode

    Although the key names can be considered to form a tree (with
    "Screen" at the root) there is no semantic relationship between
    parent and child nodes in the tree: the key "Screen" is unrelated
    to "Screen.TopEdge".  In particular, change notifications do not
    travel up the tree.

    The \ref Introspection section describes in detail how to get a list of
    existing keys and examine their capabilities.

    Programmers access the key/value pairs through instances of the
    ContextProperty class.  These instances allow applications to
    access the values and receive change notifications.

    Example:
    \code
    ContextProperty *topEdge = new ContextProperty("Screen.TopEdge");
    QObject::connect(topEdge, SIGNAL(valueChanged()),
                     this, SLOT(topEdgeChanged()));
    \endcode

    In your edgeUpChanged method you are able to retrieve the value of the property:
    \code
    qWarning() << "The edge " << topEdge->value() << " is up";
    \endcode

    Creating a ContextProperty instance for a key causes the program
    to 'subscribe' to this key.  The values for some keys might be
    expensive to determine, so you should only subscribe to those keys
    that you are currently interested in.  You can temporarily
    unsubscribe from a key without destroying the ContextProperty
    instance by using the unsubscribe() member function. Later, you
    can resume the subscription by calling the subscribe() member
    function.

    \code
    void onScreenBlank ()
    {
        topEdge->unsubscribe();
    }

    void onScreenUnblank ()
    {
        topEdge->subscribe();
    }
    \endcode

    All the context properties can be used anytime, not depending on
    whether the provider of the property is installed or running.  If
    the system/provider cannot provide you with a value, the value of
    the context property will be null.  If for some reason you are
    interested in property metadata (such as a key's current provider,
    availability, etc.) you should consult the \ref Introspection API.
*/

#include "contextproperty.h"
#include "propertyhandle.h"
#include "sconnect.h"
#include "logging.h"
#include "loggingfeatures.h"

#include <QCoreApplication>
#include <QThread>

using namespace ContextSubscriber;

/*!
   \class ContextPropertyPrivate

   \brief The private parts of the ContextProperty class.
*/

struct ContextPropertyPrivate
{
    PropertyHandle *handle; ///< The common handle behind this context property
    bool subscribed; ///< True, if we are subscribed to the handle behind us
    QVariant value; ///< Our knowledge of the value.  Needed because several
                    /// valueChanged signals might be emitted (queued) by the
                    /// PropertyHandle, without us handling them.
};

/*!
   \class ContextProperty

   \brief The ContextProperty class allows access to keys and their
   values.

   The value is available with the value() member function and change
   notifications are delivered via the valueChanged() signal.

   You can explicity subscribe and unsubscribe using the subscribe()
   and unsubscribe() member functions.  A ContextProperty is
   initially subscribed.

   When a ContextProperty is in the unsubscribed state, it usually
   keeps its last value.  This is not guaranteed however: more than
   one ContextProperty might exist in your process for the same key,
   and as long as one of them is subscribed, all of them might receive
   new values.  The valueChanged() signal is never emitted if the
   property is unsubscribed.

   A ContextProperty is generally asynchronous and relies on a running
   event loop.  Subscriptions and unsubcriptions are only handled and
   new values are only received when your program enters the event
   loop.

   ContextProperty objects can be created only after the
   Q(Core)Application is constructed.

   When a ContextProperty is first created or goes from the
   unsubcribed to the subscribed state later on, it is temporarily in
   an intermediate 'subscribing' state.  This state lasts until the
   negotiations with the provider of the key are over (or an error
   occurs) and the key's current value is known to the
   ContextProperty.

   Thus, there is a time after creating a ContextProperty (or
   subscribing it again) where value() might be out of sync with the
   provider of the key.  If you need to wait for this time to be over,
   you can not rely on the valueChanged() signal being emitted.
   This signal is only emitted when the value actually changes, which
   might not happen when subscription is over.

   Instead, you can use the waitForSubscription() member function.
   This function runs a recursive event loop, if necessary, until the
   ContextProperty is fully subscribed.

   Thus, the recommended way is to first create all ContextProperty
   instances that your program needs and QObject::connect their
   valueChanged() signals, then to call waitForSubscription() on those values
   that are needed to create the initial user interface.

   It is important to create all needed ContextProperty instances
   before calling waitForSubscription() on any of them.  Subscriptions
   are usually bundled together behind the scenes so that they can all
   be done with a single round trip to the provider.  Interleaving
   creation of ContextProperties with calls to waitForSubscription()
   would prevent this optimization.

   \note The \c ContextProperty class follows the usual QObject rules
   for non-GUI classes in multi-threaded programs.  In Qt terminology,
   the ContextProperty class is reentrant but not thread-safe.  This
   means that you can create ContextProperty instances in any thread
   and then freely use these instance in their threads, but you can
   not use a single instance concurrently from multiple threads.

   \note Please pay special attention to how signals and slots work in
   a multi-threaded program: by default, a slot is emitted in the
   thread that called QObject::connect().  For this to happen
   reliably, the thread needs to run a event loop.

   \note See the Qt documentation for \c QThread and related classes
   for more details.
 */

/// Constructs a new ContextProperty for \a key and subscribes to it.
ContextProperty::ContextProperty(const QString &key, QObject* parent)
    : QObject(parent), priv(0)
{
    priv = new ContextPropertyPrivate;

    priv->handle = PropertyHandle::instance(key);
    priv->subscribed = false;

    subscribe();
}

/// Unsubscribes from the ContextProperty and destroys it.
ContextProperty::~ContextProperty()
{
    unsubscribe();
    delete priv;
}

/// Returns the key.
QString ContextProperty::key() const
{
    return priv->handle->key();
}

/// Returns the current value.
QVariant ContextProperty::value() const
{
    return priv->handle->value();
}

/// Returns the current value, or the value \a def if the current
/// value is \c null.
QVariant ContextProperty::value(const QVariant &def) const
{
    QVariant val = priv->handle->value();
    if (val.isNull())
        return def;
    else
        return val;
}

// A safety measure to avoid emitting unnecessary signals, see
// ContextPropertyPrivate::value.
void ContextProperty::onValueChanged()
{
    QVariant newValue = priv->handle->value();
    if (priv->value != newValue ||
        priv->value.isNull() != newValue.isNull() ||
        priv->value.type() != newValue.type())
    {
        priv->value = newValue;
        Q_EMIT valueChanged();
    }
}

/// Starts subscribtion to the context property, if it isn't
/// subscribed already. If you need to wait for it to be complete, use
/// waitForSubscription().
void ContextProperty::subscribe() const
{
    if (priv->subscribed)
        return;

    // We create a queued connection, because otherwise we run
    // the users' valueChanged() handlers with locks and if they do
    // something fancy (for example unsubscribe) it can cause a
    // deadlock.
    sconnect(priv->handle, SIGNAL(valueChanged()), this, SLOT(onValueChanged()), Qt::QueuedConnection);
    priv->handle->subscribe();
    priv->subscribed = true;
}

/// Unsubscribes from the context property, if it is currently
/// subscribed. Unsubscribing informs the rest of the system that no
/// effort needs to be spent to keep the value up-to-date.  However,
/// the value might still change when it can happen 'for free'.  In
/// this case the valueChanged() signal won't be emitted.
void ContextProperty::unsubscribe() const
{
    if (!priv->subscribed)
        return;

    QObject::disconnect(priv->handle, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
    priv->handle->unsubscribe();
    priv->subscribed = false;
}

/// Suspends the execution of the current thread until subcription is
/// complete for this context property.  This might cause the main
/// event loop of your program to run and consequently signals might
/// get emitted (including the valueChanged() signal of this
/// property).  Calling this function while the subscription is not in
/// progress (because it has completed already or because the property
/// is currently unsubscribed) does nothing. Calling this function
/// from a thread which is not the main thread results in busy looping.
void ContextProperty::waitForSubscription() const
{
    if (!priv->subscribed)
        return;

    while (priv->handle->isSubscribePending()) {
        if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
            // This is not a busy loop, since the QEventLoop::WaitForMoreEvents flag
            QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
        } else {
            usleep(100000); // 0.1 second
        }
    }
}

/// Sets all of the ContextProperty instances immune to 'external
/// commanding'.  This is only intended to be used by the Context
/// Commander itself, so that it can use ContextProperties without
/// tripping over itself.  Don't use this.
void ContextProperty::ignoreCommander()
{
    PropertyHandle::ignoreCommander();
}

/// Returns the metadata about this property, please refer to \ref
/// Introspection for details.
const ContextPropertyInfo* ContextProperty::info() const
{
    return priv->handle->info();
}

/// Enables or disables all of the ContextProperty instances'
/// type-check feature.  If it is enabled and the received value from
/// the provider doesn't match the expected type, you will get an
/// error message on the stderr and the value won't be updated. If you
/// use this method, you have to use it before starting any threads.
void ContextProperty::setTypeCheck(bool newTypeCheck)
{
    PropertyHandle::setTypeCheck(newTypeCheck);
}