GCC Code Coverage Report


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