GCC Code Coverage Report


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