1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtSystems module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include <qvaluespacesubscriber.h>
35
36#include "qvaluespacemanager_p.h"
37#include "qvaluespacesubscriber_p.h"
38
39#include <QtCore/qmetaobject.h>
40#include <QtCore/qset.h>
41#include <QtCore/qstringlist.h>
42
43QT_BEGIN_NAMESPACE
44
45/*!
46 \class QValueSpaceSubscriber
47 \brief The QValueSpaceSubscriber class allows applications to read and subscribe to Value Space paths.
48 \inmodule QtPublishSubscribe
49 \ingroup publishsubscribe
50
51 By default QValueSpaceSubscriber can read values from and report change notifications for all
52 available Value Space layers. Only data from the layer with the highest priority and that
53 contains the specific key is returned by this class.
54
55 The layers that QValueSpaceSubscriber accesses can be limited by specifying either a
56 \l {QValueSpace::LayerOptions}{filter} or a QUuid at construction time.
57
58 Applications subscribe to a particular path in the Value Space. If anything under that path
59 changes the contextChanged() signal is emitted. For example given the schema:
60
61 \code
62 /Device/Buttons = 3
63 /Device/Buttons/1/Name = Menu
64 /Device/Buttons/1/Usable = true
65 /Device/Buttons/2/Name = Select
66 /Device/Buttons/2/Usable = false
67 /Device/Buttons/3/Name = Back
68 /Device/Buttons/3/Usable = true
69 \endcode
70
71 The code:
72
73 \code
74 QValueSpaceSubscriber *buttons = new QValueSpaceSubscriber("/Device/Buttons");
75 QObject::connect(buttons, SIGNAL(contentsChanged()), this, SLOT(buttonInfoChanged()));
76 \endcode
77
78 will invoke the \c {buttonInfoChanged()} slot whenever any value under \c {/Device/Buttons}
79 changes. This includes the value of \c {/Device/Buttons} itself, a change of a subpath such as
80 \c {/Device/Buttons/2/Name} or the creation or removal of a subpath.
81*/
82
83/*!
84 \fn QValueSpaceSubscriber::contentsChanged()
85
86 Emitted whenever any value under the current path changes.
87
88 \b {Note:} that if a value changes multiple times in quick succession, only the most recent
89 value may be accessible via the value() function.
90*/
91
92void QValueSpaceSubscriberPrivateProxy::handleChanged(quintptr handle)
93{
94 QAbstractValueSpaceLayer *layer = qobject_cast<QAbstractValueSpaceLayer *>(object: sender());
95
96 for (int i = 0; i < readers.count(); ++i) {
97 if (readers.at(i).first == layer && readers.at(i).second == handle) {
98 emit changed();
99 return;
100 }
101 }
102}
103
104static LayerList matchLayers(const QString &path, QValueSpace::LayerOptions filter)
105{
106 LayerList list;
107
108 QValueSpaceManager *manager = QValueSpaceManager::instance();
109 if (!manager)
110 return list;
111
112 // Invalid filter combination.
113 if ((filter & QValueSpace::PermanentLayer && filter & QValueSpace::TransientLayer)
114 || (filter & QValueSpace::WritableLayer && filter & QValueSpace::ReadOnlyLayer)) {
115 return list;
116 }
117
118 const QList<QAbstractValueSpaceLayer *> &readerList = manager->getLayers();
119
120 for (int ii = 0; ii < readerList.count(); ++ii) {
121 QAbstractValueSpaceLayer *read = readerList.at(i: ii);
122 if (filter != QValueSpace::UnspecifiedLayer &&
123 !(read->layerOptions() & filter)) {
124 continue;
125 }
126
127 QAbstractValueSpaceLayer::Handle handle =
128 read->item(parent: QAbstractValueSpaceLayer::InvalidHandle, subPath: path);
129 if (QAbstractValueSpaceLayer::InvalidHandle != handle) {
130 list.append(t: qMakePair(x: read, y: handle));
131
132 read->notifyInterest(handle, interested: true);
133 }
134 }
135
136 return list;
137}
138
139QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, QValueSpace::LayerOptions filter)
140 : path(qCanonicalPath(path))
141 , readers(matchLayers(path: this->path, filter))
142 , connections(0)
143{
144}
145
146static LayerList matchLayers(const QString &path, const QUuid &uuid)
147{
148 LayerList list;
149
150 QValueSpaceManager *manager = QValueSpaceManager::instance();
151 if (!manager)
152 return list;
153
154 const QList<QAbstractValueSpaceLayer *> &readerList = manager->getLayers();
155
156 for (int ii = 0; ii < readerList.count(); ++ii) {
157 QAbstractValueSpaceLayer *read = readerList.at(i: ii);
158 if (read->id() != uuid)
159 continue;
160
161 QAbstractValueSpaceLayer::Handle handle =
162 read->item(parent: QAbstractValueSpaceLayer::InvalidHandle, subPath: path);
163 if (QAbstractValueSpaceLayer::InvalidHandle != handle) {
164 list.append(t: qMakePair(x: read, y: handle));
165
166 read->notifyInterest(handle, interested: true);
167 }
168 }
169
170 return list;
171}
172
173QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, const QUuid &uuid)
174 : path(qCanonicalPath(path))
175 , readers(matchLayers(path: this->path, uuid))
176 , connections(0)
177{
178}
179
180QValueSpaceSubscriberPrivate::~QValueSpaceSubscriberPrivate()
181{
182 for (int ii = 0; ii < readers.count(); ++ii) {
183 readers[ii].first->notifyInterest(handle: readers[ii].second, interested: false);
184 readers[ii].first->removeHandle(handle: readers[ii].second);
185 }
186
187 if (connections)
188 delete connections;
189}
190
191void QValueSpaceSubscriberPrivate::connect(const QValueSpaceSubscriber *space) const
192{
193 QMutexLocker locker(&lock);
194
195 if (!connections) {
196 qRegisterMetaType<quintptr>(typeName: "quintptr");
197 connections = new QValueSpaceSubscriberPrivateProxy;
198 connections->readers = readers;
199 connections->connections.insert(akey: space,avalue: 1);
200 QObject::connect(sender: connections, SIGNAL(changed()),
201 receiver: space, SIGNAL(contentsChanged()));
202 for (int ii = 0; ii < readers.count(); ++ii) {
203 readers.at(i: ii).first->setProperty(handle: readers.at(i: ii).second, properties: QAbstractValueSpaceLayer::Publish);
204 QObject::connect(sender: readers.at(i: ii).first, SIGNAL(handleChanged(quintptr)), receiver: connections, SLOT(handleChanged(quintptr)));
205 }
206 } else if (!connections->connections.contains(akey: space)) {
207 connections->connections[space] = 1;
208
209 QObject::connect(sender: connections, SIGNAL(changed()), receiver: space, SIGNAL(contentsChanged()));
210 } else {
211 ++connections->connections[space];
212 }
213}
214
215bool QValueSpaceSubscriberPrivate::disconnect(QValueSpaceSubscriber * space)
216{
217 QMutexLocker locker(&lock);
218
219 if (connections) {
220 QHash<const QValueSpaceSubscriber *, int>::Iterator iter = connections->connections.find(akey: space);
221 if (iter != connections->connections.end()) {
222 --(*iter);
223 if (!*iter) {
224 QObject::disconnect(sender: connections, SIGNAL(changed()), receiver: space, SIGNAL(contentsChanged()));
225 connections->connections.erase(it: iter);
226 }
227 return true;
228 }
229 }
230 return false;
231}
232
233/*!
234 Constructs a QValueSpaceSubscriber with the specified \a parent that refers to the root path.
235
236 The constructed Value Space subscriber will access all available layers.
237*/
238QValueSpaceSubscriber::QValueSpaceSubscriber(QObject *parent)
239 : QObject(parent)
240{
241 d = new QValueSpaceSubscriberPrivate(QLatin1String("/"));
242}
243
244/*!
245 Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path.
246
247 The constructed Value Space subscriber will access all available layers.
248*/
249QValueSpaceSubscriber::QValueSpaceSubscriber(const QString &path, QObject *parent)
250 : QObject(parent)
251{
252 d = new QValueSpaceSubscriberPrivate(path);
253}
254
255/*!
256 Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. The
257 \a filter parameter is used to limit which layers this QValueSpaceSubscriber will access.
258
259 If a layer matching \a filter is not found, the constructed QValueSpaceSubscriber will be
260 unconnected.
261
262 \sa isConnected()
263*/
264QValueSpaceSubscriber::QValueSpaceSubscriber(QValueSpace::LayerOptions filter, const QString &path, QObject *parent)
265 : QObject(parent)
266{
267 d = new QValueSpaceSubscriberPrivate(path, filter);
268}
269
270/*!
271 Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. This
272 QValueSpaceSubscriber will only use the layer identified by \a uuid.
273
274 Use of this constructor is not platform agnostic. If possible use one of the constructors that
275 take a QValueSpace::LayerOptions parameter instead.
276
277 If a layer with a matching \a uuid is not found, the constructed QValueSpaceSubscriber will be
278 unconnected.
279
280 \sa QValueSpace, isConnected()
281*/
282QValueSpaceSubscriber::QValueSpaceSubscriber(const QUuid &uuid, const QString &path, QObject *parent)
283 : QObject(parent)
284{
285 d = new QValueSpaceSubscriberPrivate(path, uuid);
286}
287
288/*!
289 Destroys the QValueSpaceSubscriber.
290*/
291QValueSpaceSubscriber::~QValueSpaceSubscriber()
292{
293 if (!isConnected())
294 return;
295
296 d->disconnect(space: this);
297}
298
299/*!
300 \property QValueSpaceSubscriber::path
301
302 This property holds the current path that the QValueSpaceSubscriber refers to.
303
304 Settings this property causes the QValueSpaceSubscriber to disconnect and reconnect to the
305 Value Space with the new path. As a result all signal/slot connections are disconnected.
306*/
307void QValueSpaceSubscriber::setPath(const QString &path)
308{
309 if (!isConnected()) {
310 qWarning(msg: "setPath called on unconnected QValueSpaceSubscriber.");
311 return;
312 }
313
314 if (this->path() == path)
315 return;
316
317 d->disconnect(space: this);
318
319 disconnect();
320
321 d = new QValueSpaceSubscriberPrivate(path);
322}
323
324QString QValueSpaceSubscriber::path() const
325{
326 return d->path;
327}
328
329/*!
330 Sets the path to the same path as \a subscriber.
331
332 Calling this function causes the QValueSpaceSubscriber to disconnect and reconnect to the value
333 space with the specified \a path.
334
335 Calling this function disconnects all signal/slot connections.
336*/
337void QValueSpaceSubscriber::setPath(QValueSpaceSubscriber *subscriber)
338{
339 if (!isConnected()) {
340 qWarning(msg: "setPath called on unconnected QValueSpaceSubscriber.");
341 return;
342 }
343
344 d->disconnect(space: this);
345
346 disconnect();
347
348 d = subscriber->d;
349}
350
351/*!
352 Changes the path to the absolute path if \a path starts with a '/'; otherwise changes to the
353 sub path of the current path.
354*/
355void QValueSpaceSubscriber::cd(const QString &path)
356{
357 if (!isConnected()) {
358 qWarning(msg: "cd called on unconnected QValueSpaceSubscriber.");
359 return;
360 }
361
362 if (path.startsWith(c: QLatin1Char('/')))
363 setPath(path);
364 else
365 setPath(this->path() + QLatin1Char('/') + path);
366}
367
368/*!
369 Sets the path to parent of the current path.
370*/
371void QValueSpaceSubscriber::cdUp()
372{
373 if (!isConnected()) {
374 qWarning(msg: "cdUp called on unconnected QValueSpaceSubscriber.");
375 return;
376 }
377
378 if (path() == QLatin1String("/"))
379 return;
380
381 QString p(path());
382
383 int index = p.lastIndexOf(c: QLatin1Char('/'));
384
385 p.truncate(pos: index);
386
387 setPath(p);
388}
389
390/*!
391 Returns true if this QValueSpaceSubscriber is connected to at least one available layer;
392 otherwise returns false. An unconnected QValueSpaceSubscriber is constructed if the filtering
393 parameters passed to the constructor eliminate all available layers.
394*/
395bool QValueSpaceSubscriber::isConnected() const
396{
397 return !d->readers.isEmpty();
398}
399
400/*!
401 Returns the value of the \a subPath under this subscriber path, or the value of this subscriber
402 path if \a subPath is empty. If the value does not exists \a def is returned.
403
404 The following code shows how the subscriber path and \a subPath relate.
405
406 \code
407 QValueSpaceSubscriber base("/Settings");
408 QValueSpaceSubscriber equiv("/Settings/QtProject/General/Mappings");
409
410 // Is true
411 equiv.value() == base.value("QtProject/General/Mappings");
412 \endcode
413*/
414QVariant QValueSpaceSubscriber::value(const QString & subPath, const QVariant &def) const
415{
416 if (!isConnected()) {
417 qWarning(msg: "value called on unconnected QValueSpaceSubscriber.");
418 return QVariant();
419 }
420
421 QVariant value;
422 if (subPath.isEmpty()) {
423 for (int ii = d->readers.count(); ii > 0; --ii) {
424 if (d->readers[ii - 1].first->value(handle: d->readers[ii - 1].second, data: &value))
425 return value;
426 }
427 } else {
428 const QString vpath(qCanonicalPath(path: subPath));
429 for (int ii = d->readers.count(); ii > 0; --ii) {
430 if (d->readers[ii - 1].first->value(handle: d->readers[ii - 1].second, subPath: vpath, data: &value))
431 return value;
432 }
433 }
434 return def;
435}
436
437/*!
438 \property QValueSpaceSubscriber::value
439
440 This property holds the value of the path that this QValueSpaceSubscriber refers to.
441*/
442QVariant QValueSpaceSubscriber::valuex(const QVariant &def) const
443{
444 QMutexLocker locker(&d->lock);
445
446 if (!d->connections || d->connections->connections.value(akey: this) == 0) {
447 locker.unlock();
448 d->connect(space: this);
449 }
450
451 return value(subPath: QString(), def);
452}
453
454/*!
455 \reimp
456*/
457void QValueSpaceSubscriber::connectNotify(const QMetaMethod &signal)
458{
459 static const QMetaMethod contentsChangedSignal = QMetaMethod::fromSignal(signal: &QValueSpaceSubscriber::contentsChanged);
460 if (isConnected() && signal == contentsChangedSignal)
461 d->connect(space: this);
462}
463
464/*!
465 \reimp
466*/
467void QValueSpaceSubscriber::disconnectNotify(const QMetaMethod &signal)
468{
469 static const QMetaMethod contentsChangedSignal = QMetaMethod::fromSignal(signal: &QValueSpaceSubscriber::contentsChanged);
470 if (isConnected() && signal == contentsChangedSignal)
471 d->disconnect(space: this);
472}
473
474/*!
475 Returns a list of sub-paths under the current path. For example, given a Value Space tree
476 containing:
477
478 \code
479 /Settings/QtProject/Device
480 /Settings/QtProject/Other
481 /Settings/Qt
482 /Device/Buttons
483 \endcode
484
485 \c { QValueSpaceSubscriber("/Settings").subPaths() } will return a list containing
486 \c { { QtProject, Qt } } in no particular order.
487*/
488QStringList QValueSpaceSubscriber::subPaths() const
489{
490 if (!isConnected()) {
491 qWarning(msg: "subPaths called on unconnected QValueSpaceSubscriber.");
492 return QStringList();
493 }
494
495 QSet<QString> rv;
496 for (int ii = 0; ii < d->readers.count(); ++ii)
497 rv.unite(other: d->readers[ii].first->children(handle: d->readers[ii].second));
498
499 QStringList rvs;
500 for (QSet<QString>::ConstIterator iter = rv.begin(); iter != rv.end(); ++iter)
501 rvs.append(t: *iter);
502
503 return rvs;
504}
505
506QT_END_NAMESPACE
507

source code of qtsystems/src/publishsubscribe/qvaluespacesubscriber.cpp