GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModelImpl.cpp
Date: 2025-02-23 04:08:29
Exec Total Coverage
Lines: 0 196 0.0%
Branches: 0 362 0.0%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * Copyright (C) 2009 - 2022 Florian Pose <fp@igh.de>
4 *
5 * This file is part of the QtPdCom library.
6 *
7 * The QtPdCom library is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * The QtPdCom library is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with the QtPdCom Library. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ****************************************************************************/
21
22 #include "MessageModelImpl.h"
23
24 #include "MessageImpl.h"
25 #include "MessageItem.h"
26
27 #include <QDateTime>
28 #include <QFutureWatcher>
29 #include <QtPdCom1/Process.h>
30
31 using QtPdCom::MessageModel;
32
33 /****************************************************************************/
34
35 /** Constructor.
36 */
37 MessageModel::Impl::Impl(MessageModel *model):
38 parent {model},
39 announcedMessageItem {nullptr},
40 messageManager {nullptr},
41 rowLimit {1000},
42 canFetchMore {false},
43 historicSeqNo {0},
44 lessThan {MessageItem::levelNoLessThan}
45 {}
46
47 /****************************************************************************/
48
49 /** Destructor.
50 */
51 MessageModel::Impl::~Impl()
52 {}
53
54 /****************************************************************************/
55
56 /** Insert a message item.
57 */
58 void MessageModel::Impl::insertItem(MessageItem *msgItem)
59 {
60 int row = messageItemList.indexOf(msgItem);
61
62 if (row >= 0) {
63 parent->beginRemoveRows(QModelIndex(), row, row);
64 messageItemList.removeAt(row);
65 parent->endRemoveRows();
66 }
67
68 row = (messageItemList.empty()
69 or MessageItem::lessThan(msgItem, messageItemList.first()))
70 ? 0
71 : (std::lower_bound(
72 messageItemList.begin(),
73 messageItemList.end(),
74 msgItem,
75 lessThan)
76 - messageItemList.begin());
77
78 parent->beginInsertRows(QModelIndex(), row, row);
79 messageItemList.insert(row, msgItem);
80 parent->endInsertRows();
81
82 // Is this a candidate for the current/announced message?
83 //
84 // Announce this message as current message, if
85 // - there is either no message announced yet,
86 // - the message type is more important than the type of the announced one
87 // - the sequence number is earlier
88 if (msgItem->isActive()
89 and (!announcedMessageItem
90 or msgItem->getType() > announcedMessageItem->getType()
91 or (msgItem->getType() == announcedMessageItem->getType()
92 and seqNoLessThan(
93 msgItem->seqNo,
94 announcedMessageItem->seqNo)))) {
95 announcedMessageItem = msgItem;
96 #if PD_DEBUG_MESSAGE_MODEL
97 qDebug() << __func__ << "currentMessage" << msgItem
98 << msgItem->message;
99 #endif
100 emit parent->currentMessage(announcedMessageItem->message);
101 }
102 else if (msgItem->resetTime and msgItem == announcedMessageItem) {
103 // search for a new message to announce
104 announce();
105 }
106 }
107
108 /****************************************************************************/
109
110 /** Called from the PdCom interface, if a new message appears via
111 * processMessage() or in context of activeMessagesReply().
112 */
113 void MessageModel::Impl::addProcessMessage(const PdCom::Message &pdComMsg)
114 {
115 QString path {QString::fromStdString(pdComMsg.path)};
116 #if PD_DEBUG_MESSAGE_MODEL
117 qDebug() << __func__ << "seqno" << pdComMsg.seqNo;
118 #endif
119
120 Message *&msg = messageMap[path][pdComMsg.index];
121 if (!msg) {
122 #if PD_DEBUG_MESSAGE_MODEL
123 qDebug() << __func__ << "not in map. creating new message.";
124 #endif
125 msg = new Message();
126 msg->impl->fromPdComMessage(pdComMsg);
127 }
128
129 auto msgItem {msg->impl->currentItem};
130 #if PD_DEBUG_MESSAGE_MODEL
131 qDebug() << __func__ << "msgItem" << msgItem;
132 if (msgItem) {
133 qDebug() << __func__ << "currentItem seqNo" << msgItem->seqNo;
134 }
135 #endif
136
137 if (pdComMsg.level != PdCom::LogLevel::Reset) { // set
138
139 if (not msg->impl->announced) {
140 emit parent->anyMessage(msg);
141 msg->impl->announced = true;
142 }
143
144 if (msgItem) {
145 // already has current item
146 if (!msgItem->seqNo) {
147 // Current item has zero seqNo -> from mixed mode.
148 // Just attach the seqNo.
149 msgItem->seqNo = pdComMsg.seqNo;
150 }
151 else if (msgItem->seqNo != pdComMsg.seqNo) {
152 // Current item has different seqNo.
153 // Not ours, thus create a new one.
154 msgItem = new MessageItem(msg, this, pdComMsg.time.count());
155 msgItem->seqNo = pdComMsg.seqNo;
156 }
157 }
158 else { // no current item
159 msgItem = new MessageItem(msg, this, pdComMsg.time.count());
160 msgItem->seqNo = pdComMsg.seqNo;
161 msg->impl->currentItem = msgItem;
162 }
163 insertItem(msgItem);
164 }
165 else { // reset
166 #if PD_DEBUG_MESSAGE_MODEL
167 qDebug() << __func__ << "reset msgItem" << msgItem;
168 #endif
169 msg->impl->announced = false;
170
171 if (msgItem) {
172 msgItem->resetTime = pdComMsg.time.count();
173
174 // notify views
175 int row = messageItemList.indexOf(msgItem);
176 #if PD_DEBUG_MESSAGE_MODEL
177 qDebug() << __func__ << "reset row" << row;
178 #endif
179 if (row >= 0) {
180 QModelIndex idx0 = parent->index(row, 0);
181 QModelIndex idx1 = parent->index(row, 2);
182 emit parent->dataChanged(idx0, idx1);
183 }
184 msg->impl->currentItem = nullptr;
185 if (msgItem == announcedMessageItem) {
186 announce();
187 }
188 }
189 }
190 }
191
192 /****************************************************************************/
193
194 /** Called from the PdCom interface, if a historic message appears via
195 * getMessageReply().
196 */
197 void MessageModel::Impl::addHistoricMessage(
198 const PdCom::Message &pdComMsg,
199 const PdCom::Message &resetMsg)
200 {
201 QString path {QString::fromStdString(pdComMsg.path)};
202 #if PD_DEBUG_MESSAGE_MODEL
203 qDebug() << __func__ << pdComMsg.seqNo << resetMsg.seqNo;
204 #endif
205
206 Message *&msg = messageMap[path][pdComMsg.index];
207 if (!msg) {
208 msg = new Message();
209 msg->impl->fromPdComMessage(pdComMsg);
210 }
211
212 auto msgItem = new MessageItem(msg, this, pdComMsg.time.count());
213 msgItem->seqNo = pdComMsg.seqNo;
214 msgItem->resetTime = resetMsg.time.count();
215 insertItem(msgItem);
216 }
217
218 /****************************************************************************/
219
220 /** Returns a wrapped version of a string.
221 */
222 QString MessageModel::Impl::wrapText(const QString &text, unsigned int width)
223 {
224 QString ret;
225 int lineOffset, i;
226
227 lineOffset = 0;
228 while (lineOffset + (int) width < text.length()) {
229 // search last space before line end
230 for (i = width; i >= 0; i--) {
231 if (text[lineOffset + i].isSpace()) {
232 break; // break at whitespace
233 }
234 }
235 if (i < 0) { // no whitespace found
236 i = width; // "hard" break at line end
237 }
238
239 ret += text.mid(lineOffset, i) + QChar(QChar::LineSeparator);
240 lineOffset += i + 1; // skip line and whitespace
241 }
242
243 ret += text.mid(lineOffset); // append remaining string
244 return ret;
245 }
246
247 /****************************************************************************/
248
249 void MessageModel::Impl::getHistoryMessage()
250 {
251 if (!messageManager) {
252 qWarning() << __func__ << "no message manager";
253 return;
254 }
255
256 #if PD_DEBUG_MESSAGE_MODEL
257 qDebug() << __func__ << "setting canFetchMore to false";
258 #endif
259 canFetchMore = false; // avoid fetchMore called twice for same seqNo
260
261 uint32_t prevSeqNo = historicSeqNo - 1;
262 #if PD_DEBUG_MESSAGE_MODEL
263 qDebug() << __func__ << "fetching" << prevSeqNo;
264 #endif
265
266 try {
267 messageManager->getMessage(prevSeqNo, this, &Impl::getMessageReply);
268 }
269 catch (PdCom::Exception &e) {
270 qDebug() << __func__ << e.what();
271 }
272 }
273
274 /****************************************************************************/
275
276 void MessageModel::Impl::announce()
277 {
278 MessageItemList sortedList(messageItemList);
279 std::sort(
280 sortedList.begin(),
281 sortedList.end(),
282 MessageItem::levelNoLessThan);
283
284 // if there is no active message, announce a nullptr
285 if (not sortedList.front()->isActive()) {
286 if (announcedMessageItem) {
287 announcedMessageItem = nullptr;
288 #if PD_DEBUG_MESSAGE_MODEL
289 qDebug() << __func__ << "currentMessage null";
290 #endif
291 emit parent->currentMessage(nullptr);
292 }
293 return;
294 }
295
296 // the first active message in the sorted list determines the type
297 auto type = sortedList.front()->getType();
298
299 // find the earliest message with this type
300 MessageItem *candidate {nullptr};
301 for (MessageItemList::const_iterator it = sortedList.begin();
302 (it != sortedList.end() and (*it)->isActive()
303 and (*it)->getType() == type);
304 ++it) {
305 candidate = *it;
306 }
307
308 if (announcedMessageItem == candidate) {
309 // still the same message, no new announcement needed
310 return;
311 }
312 announcedMessageItem = candidate;
313
314 const QtPdCom::Message *msg {nullptr};
315 if (announcedMessageItem) {
316 msg = announcedMessageItem->message;
317 }
318 #if PD_DEBUG_MESSAGE_MODEL
319 qDebug() << __func__ << "currentMessage" << announcedMessageItem << msg;
320 #endif
321 emit parent->currentMessage(msg);
322 }
323
324 /*****************************************************************************
325 * private slots
326 ****************************************************************************/
327
328 /** Reacts on process values changes of all messages to watch.
329 */
330 void MessageModel::Impl::stateChanged()
331 {
332 Message *msg = (Message *) sender();
333 DoubleVariable &var = msg->impl->variable;
334 double time {var.hasData() ? var.getValue() : 0.0};
335
336 #if PD_DEBUG_MESSAGE_MODEL
337 qDebug() << __func__ << msg->getPath() << msg->getIndex() << time;
338 #endif
339
340 if (time and not msg->impl->announced) {
341 emit parent->anyMessage(msg);
342 msg->impl->announced = true;
343 }
344
345 MessageItem *msgItem {nullptr};
346
347 if (time) { // set
348 MessageItem *msgItem {msg->impl->currentItem};
349 if (!msgItem) {
350 msgItem = new MessageItem(msg, this, time * 1e9);
351 #if PD_DEBUG_MESSAGE_MODEL
352 qDebug() << __func__ << "new msgItem" << msgItem;
353 #endif
354 msg->impl->currentItem = msgItem;
355 insertItem(msgItem);
356 }
357 }
358 else { // reset
359 msg->impl->announced = false;
360 msgItem = msg->impl->currentItem;
361 if (msgItem) {
362 auto now {QDateTime::currentDateTime()};
363 msgItem->resetTime = now.toMSecsSinceEpoch() * 1000000U;
364
365 // notify views
366 int row = messageItemList.indexOf(msgItem);
367 #if PD_DEBUG_MESSAGE_MODEL
368 qDebug() << __func__ << "reset row" << row;
369 #endif
370 if (row >= 0) {
371 QModelIndex idx0 = parent->index(row, 0);
372 QModelIndex idx1 = parent->index(row, 2);
373 emit parent->dataChanged(idx0, idx1);
374 }
375
376 msg->impl->currentItem = nullptr;
377 if (msgItem == announcedMessageItem) {
378 announce();
379 }
380 }
381 }
382 }
383
384 /****************************************************************************/
385
386 void MessageModel::Impl::processMessage(PdCom::Message message)
387 {
388 #if PD_DEBUG_MESSAGE_MODEL
389 auto path = QString::fromStdString(message.path);
390 auto text = QString::fromStdString(message.text);
391 qDebug() << __func__;
392 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
393 << "path" << path << "time" << message.time.count() << "index"
394 << message.index << "text" << text;
395 #endif
396
397 addProcessMessage(message);
398 }
399
400 /****************************************************************************/
401
402 void MessageModel::Impl::getMessageReply(PdCom::Message message)
403 {
404 auto path = QString::fromStdString(message.path);
405
406 #if PD_DEBUG_MESSAGE_MODEL
407 auto text = QString::fromStdString(message.text);
408 qDebug() << __func__;
409 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
410 << "path" << path << "time" << message.time.count() << "index"
411 << message.index << "text" << text;
412 #endif
413
414 if (path.isEmpty()) {
415 // EOF marker - no more messages from process
416 return;
417 }
418
419 historicSeqNo = message.seqNo;
420
421 canFetchMore = message.level != PdCom::LogLevel::Reset;
422 #if PD_DEBUG_MESSAGE_MODEL
423 qDebug() << __func__ << "setting canFetchMore to" << canFetchMore;
424 #endif
425
426 bool stillActive {false};
427 if (canFetchMore) {
428 // found a message that was set in the past. try to find the reset.
429 bool found {false};
430 #if PD_DEBUG_MESSAGE_MODEL
431 qDebug() << __func__ << "reset msg list size is"
432 << resetMessagesList.size();
433 #endif
434 for (auto r = resetMessagesList.begin(); r != resetMessagesList.end();
435 r++) {
436 if (r->path == message.path and r->index == message.index) {
437 addHistoricMessage(message, *r);
438 resetMessagesList.erase(r);
439 #if PD_DEBUG_MESSAGE_MODEL
440 found = true;
441 #endif
442 break;
443 }
444 }
445 #if PD_DEBUG_MESSAGE_MODEL
446 if (!found) {
447 qDebug() << __func__ << "reset message not found for"
448 << message.seqNo;
449 // found a message that seems to be still active. Go on with
450 // reading, otherwise views won't ask for more data
451 stillActive = true;
452 }
453 #endif
454 }
455 else {
456 resetMessagesList.append(message);
457 }
458
459 if ((!canFetchMore or stillActive) and historicSeqNo) {
460 getHistoryMessage();
461 }
462 }
463
464 /****************************************************************************/
465
466 void MessageModel::Impl::activeMessagesReply(
467 std::vector<PdCom::Message> messageList)
468 {
469 quint32 maxSeqNo {0};
470
471 #if PD_DEBUG_MESSAGE_MODEL
472 qDebug().nospace() << __func__ << "(" << messageList.size() << ")";
473 #endif
474 for (auto message : messageList) {
475 #if PD_DEBUG_MESSAGE_MODEL
476 auto path = QString::fromStdString(message.path);
477 auto text = QString::fromStdString(message.text);
478 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
479 << "path" << path << "time" << message.time.count()
480 << "index" << message.index << "text" << text;
481 #endif
482 if (message.level != PdCom::LogLevel::Reset) {
483 addProcessMessage(message);
484 }
485 else {
486 // one entry in the list of active messages can be a reset
487 // message, to announce the current sequence number
488 resetMessagesList.append(message);
489 }
490
491 if (message.seqNo > maxSeqNo) {
492 maxSeqNo = message.seqNo;
493 }
494 }
495
496 if (maxSeqNo > 0) {
497 // now fetch one historic message
498 historicSeqNo = maxSeqNo;
499 getHistoryMessage();
500 }
501 }
502
503 /****************************************************************************/
504
505 void MessageModel::Impl::processReset()
506 {
507 #if PD_DEBUG_MESSAGE_MODEL
508 qDebug() << __func__;
509 #endif
510
511 canFetchMore = false;
512 historicSeqNo = 0;
513 resetMessagesList.clear();
514
515 parent->beginResetModel();
516 messageItemList.clear();
517
518 // reset current item pointers
519 for (auto hash : messageMap) {
520 for (auto msg : hash) {
521 if (msg->impl->currentItem) {
522 msg->impl->currentItem = nullptr;
523 }
524 }
525 }
526 parent->endResetModel();
527
528 if (messageManager) {
529 QObject::disconnect(
530 messageManager,
531 &MessageManager::processMessageSignal,
532 this,
533 &Impl::processMessage);
534 QObject::disconnect(
535 process,
536 &Process::processConnected,
537 this,
538 &Impl::reloadActiveMessages);
539 QObject::disconnect(
540 messageManager,
541 &MessageManager::processResetSignal,
542 this,
543 &Impl::processReset);
544 }
545 }
546
547 /****************************************************************************/
548
549
550 void MessageModel::Impl::reloadActiveMessages()
551 {
552 if (!messageManager) {
553 return;
554 }
555 messageManager->activeMessages(this, &Impl::activeMessagesReply);
556 }
557