1 /*
2   propertysyncer.cpp
3 
4   This file is part of GammaRay, the Qt application inspection and
5   manipulation tool.
6 
7   Copyright (C) 2015-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8   Author: Volker Krause <volker.krause@kdab.com>
9 
10   Licensees holding valid commercial KDAB GammaRay licenses may use this file in
11   accordance with GammaRay Commercial License Agreement provided with the Software.
12 
13   Contact info@kdab.com if any conditions of this licensing are not clear to you.
14 
15   This program is free software; you can redistribute it and/or modify
16   it under the terms of the GNU General Public License as published by
17   the Free Software Foundation, either version 2 of the License, or
18   (at your option) any later version.
19 
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24 
25   You should have received a copy of the GNU General Public License
26   along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 */
28 
29 #include "propertysyncer.h"
30 #include "message.h"
31 
32 #include <compat/qasconst.h>
33 
34 #include <QDebug>
35 #include <QMetaProperty>
36 
37 #include <algorithm>
38 
39 using namespace GammaRay;
40 
qobjectPropertyOffset()41 static int qobjectPropertyOffset()
42 {
43     return QObject::staticMetaObject.propertyCount();
44 }
45 
PropertySyncer(QObject * parent)46 PropertySyncer::PropertySyncer(QObject *parent)
47     : QObject(parent)
48     , m_address(Protocol::InvalidObjectAddress)
49     , m_initialSync(false)
50 {
51 }
52 
53 PropertySyncer::~PropertySyncer() = default;
54 
setRequestInitialSync(bool initialSync)55 void PropertySyncer::setRequestInitialSync(bool initialSync)
56 {
57     m_initialSync = initialSync;
58 }
59 
addObject(Protocol::ObjectAddress addr,QObject * obj)60 void PropertySyncer::addObject(Protocol::ObjectAddress addr, QObject *obj)
61 {
62     Q_ASSERT(addr != Protocol::InvalidObjectAddress);
63     Q_ASSERT(obj);
64     if (qobjectPropertyOffset() == obj->metaObject()->propertyCount())
65         return; // no properties we could sync
66 
67     for (int i = qobjectPropertyOffset(); i < obj->metaObject()->propertyCount(); ++i) {
68         const auto prop = obj->metaObject()->property(i);
69         if (!prop.hasNotifySignal())
70             continue;
71         connect(obj, QByteArray("2") + prop.notifySignal().methodSignature(), this, SLOT(propertyChanged()));
72     }
73 
74     connect(obj, &QObject::destroyed, this, &PropertySyncer::objectDestroyed);
75 
76     ObjectInfo info;
77     info.addr = addr;
78     info.obj = obj;
79     info.recursionLock = false;
80     info.enabled = false;
81     m_objects.push_back(info);
82 }
83 
setObjectEnabled(Protocol::ObjectAddress addr,bool enabled)84 void PropertySyncer::setObjectEnabled(Protocol::ObjectAddress addr, bool enabled)
85 {
86     const auto it = std::find_if(m_objects.begin(), m_objects.end(), [addr](
87                                      const ObjectInfo &info) {
88         return info.addr == addr;
89     });
90     if (it == m_objects.end() || (*it).enabled == enabled)
91         return;
92 
93     (*it).enabled = enabled;
94     if (enabled && m_initialSync) {
95         Message msg(m_address, Protocol::PropertySyncRequest);
96         msg << addr;
97         emit message(msg);
98     }
99 }
100 
address() const101 Protocol::ObjectAddress PropertySyncer::address() const
102 {
103     return m_address;
104 }
105 
setAddress(Protocol::ObjectAddress addr)106 void PropertySyncer::setAddress(Protocol::ObjectAddress addr)
107 {
108     m_address = addr;
109 }
110 
handleMessage(const GammaRay::Message & msg)111 void PropertySyncer::handleMessage(const GammaRay::Message &msg)
112 {
113     Q_ASSERT(msg.address() == m_address);
114     switch (msg.type()) {
115     case Protocol::PropertySyncRequest:
116     {
117         Protocol::ObjectAddress addr;
118         msg >> addr;
119         Q_ASSERT(addr != Protocol::InvalidObjectAddress);
120 
121         const auto it
122             = std::find_if(m_objects.constBegin(), m_objects.constEnd(),
123                            [addr](const ObjectInfo &info) {
124                 return info.addr == addr;
125             });
126         if (it == m_objects.constEnd())
127             break;
128 
129         QVector<QPair<QByteArray, QVariant> > values;
130         const auto propCount = (*it).obj->metaObject()->propertyCount();
131         values.reserve(propCount);
132         for (int i = qobjectPropertyOffset(); i < propCount; ++i) {
133             const auto prop = (*it).obj->metaObject()->property(i);
134             values.push_back(qMakePair(QByteArray(prop.name()), prop.read((*it).obj)));
135         }
136         Q_ASSERT(!values.isEmpty());
137 
138         Message msg(m_address, Protocol::PropertyValuesChanged);
139         msg << addr << (quint32)values.size();
140         for (const auto &value : qAsConst(values))
141             msg << value.first << value.second;
142         emit message(msg);
143         break;
144     }
145     case Protocol::PropertyValuesChanged:
146     {
147         Protocol::ObjectAddress addr;
148         quint32 changeSize;
149         msg >> addr >> changeSize;
150         Q_ASSERT(addr != Protocol::InvalidObjectAddress);
151         Q_ASSERT(changeSize > 0);
152 
153         auto it = std::find_if(m_objects.begin(), m_objects.end(), [addr](const ObjectInfo &info) {
154                 return info.addr == addr;
155             });
156         if (it == m_objects.end())
157             break;
158 
159         for (quint32 i = 0; i < changeSize; ++i) {
160             QByteArray propName;
161             QVariant propValue;
162             msg >> propName >> propValue;
163             (*it).recursionLock = true;
164             (*it).obj->setProperty(propName, propValue);
165 
166             // it can be invalid if as a result of the above call new objects have been registered for example
167             it = std::find_if(m_objects.begin(), m_objects.end(), [addr](const ObjectInfo &info) {
168                     return info.addr == addr;
169                 });
170             Q_ASSERT(it != m_objects.end());
171             (*it).recursionLock = false;
172         }
173         break;
174     }
175     default:
176         Q_ASSERT_X(false, "PropertySyncer::handleMessage",
177                    "Unexpected Gammaray::Message type encountered");
178     }
179 }
180 
propertyChanged()181 void PropertySyncer::propertyChanged()
182 {
183     const auto *obj = sender();
184     Q_ASSERT(obj);
185     const auto it
186         = std::find_if(m_objects.constBegin(), m_objects.constEnd(), [obj](const ObjectInfo &info) {
187         return info.obj == obj;
188     });
189     Q_ASSERT(it != m_objects.constEnd());
190 
191     if ((*it).recursionLock || !(*it).enabled)
192         return;
193 
194     const auto sigIndex = senderSignalIndex();
195     QVector<QPair<QByteArray, QVariant> > changes;
196     for (int i = qobjectPropertyOffset(); i < obj->metaObject()->propertyCount(); ++i) {
197         const auto prop = obj->metaObject()->property(i);
198         if (prop.notifySignalIndex() != sigIndex)
199             continue;
200         changes.push_back(qMakePair(QByteArray(prop.name()), prop.read(obj)));
201     }
202     Q_ASSERT(!changes.isEmpty());
203 
204     Message msg(m_address, Protocol::PropertyValuesChanged);
205     msg << (*it).addr << (quint32)changes.size();
206     for (const auto &change : qAsConst(changes))
207         msg << change.first << change.second;
208     emit message(msg);
209 }
210 
objectDestroyed(QObject * obj)211 void PropertySyncer::objectDestroyed(QObject *obj)
212 {
213     const auto it = std::find_if(m_objects.begin(), m_objects.end(), [obj](const ObjectInfo &info) {
214         return info.obj == obj;
215     });
216     Q_ASSERT(it != m_objects.end());
217     m_objects.erase(it);
218 }
219