File indexing completed on 2024-05-19 05:42:13

0001 #include <ct_lvtmdl_debugmodel.h>
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <QDebug>
0021 #include <QDir>
0022 #include <QLoggingCategory>
0023 #include <QMutex>
0024 
0025 #include <preferences.h>
0026 
0027 Q_LOGGING_CATEGORY(debugModel, "lakoswidgets.lvtmdl.basetablemodel")
0028 
0029 namespace {
0030 
0031 QString msgTypeToString(int type)
0032 {
0033     switch (type) {
0034     case QtMsgType::QtCriticalMsg:
0035         return "Critical";
0036     case QtMsgType::QtDebugMsg:
0037         return "Debug";
0038     case QtMsgType::QtFatalMsg:
0039         return "Fatal";
0040     case QtMsgType::QtInfoMsg:
0041         return "Info";
0042     case QtMsgType::QtWarningMsg:
0043         return "Warning";
0044     }
0045     return {};
0046 }
0047 
0048 } // namespace
0049 namespace Codethink::lvtmdl {
0050 
0051 static DebugModel *self = nullptr; // NOLINT
0052 
0053 struct DebugModel::Private {
0054     constexpr static int BUFFER_ELEMENTS = 5000;
0055 
0056     std::vector<DebugData> data;
0057     QMutex mtx;
0058 
0059     Private(): data(BUFFER_ELEMENTS){};
0060 };
0061 
0062 DebugModel::DebugModel(): d(std::make_unique<DebugModel::Private>())
0063 {
0064     if (self) {
0065         qCDebug(debugModel) << "Only one Debug Model should exist at a given time";
0066     }
0067     self = this;
0068 }
0069 
0070 DebugModel::~DebugModel()
0071 {
0072     self = nullptr;
0073 }
0074 
0075 void DebugModel::addData(const DebugData& data)
0076 {
0077     QMutexLocker locker(&self->d->mtx);
0078 
0079     beginInsertRows(QModelIndex(), rowCount(), rowCount());
0080     d->data.push_back(data);
0081     endInsertRows();
0082 }
0083 
0084 int DebugModel::rowCount(const QModelIndex& parent) const
0085 {
0086     Q_UNUSED(parent);
0087     return (int) d->data.size();
0088 }
0089 
0090 int DebugModel::columnCount(const QModelIndex& parent) const
0091 {
0092     Q_UNUSED(parent);
0093     return DebugModel::Columns::COUNT;
0094 }
0095 
0096 QVariant DebugModel::headerData(int section, Qt::Orientation orientation, int role) const
0097 {
0098     if (orientation == Qt::Orientation::Vertical) {
0099         return {};
0100     }
0101 
0102     if (role != Qt::DisplayRole) {
0103         return {};
0104     }
0105 
0106     switch (section) {
0107     case DebugModel::Columns::e_CATEGORY:
0108         return tr("Category");
0109     case DebugModel::Columns::e_MESSAGE:
0110         return tr("Message");
0111     case DebugModel::Columns::e_FILE:
0112         return tr("File");
0113     case DebugModel::Columns::e_FUNCTION:
0114         return tr("Function");
0115     case DebugModel::Columns::e_LINE:
0116         return tr("Line");
0117     case DebugModel::Columns::e_VERSION:
0118         return tr("Version");
0119     case DebugModel::Columns::e_TYPE:
0120         return tr("Type");
0121     }
0122     return {};
0123 }
0124 
0125 QVariant DebugModel::data(const QModelIndex& idx, int role) const
0126 {
0127     if (!idx.isValid()) {
0128         return {};
0129     }
0130 
0131     if (idx.row() >= rowCount()) {
0132         return {};
0133     }
0134 
0135     const DebugData data = d->data[idx.row()];
0136     if (role == Qt::DisplayRole) {
0137         switch (idx.column()) {
0138         case DebugModel::Columns::e_CATEGORY:
0139             return data.category;
0140         case DebugModel::Columns::e_MESSAGE:
0141             return data.message;
0142         case DebugModel::Columns::e_FILE:
0143             return data.file;
0144         case DebugModel::Columns::e_FUNCTION:
0145             return data.function;
0146         case DebugModel::Columns::e_LINE:
0147             return data.line;
0148         case DebugModel::Columns::e_VERSION:
0149             return data.version;
0150         case DebugModel::Columns::e_TYPE:
0151             return msgTypeToString(data.type);
0152         }
0153     }
0154 
0155     return {};
0156 }
0157 
0158 void DebugModel::clear()
0159 {
0160     beginResetModel();
0161     d->data.clear();
0162     d->data.resize(0);
0163     endResetModel();
0164 }
0165 
0166 bool DebugModel::saveAs(const QString& filename) const
0167 {
0168     QFile dump(filename);
0169     QTextStream textStream(&dump);
0170 
0171     if (!dump.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
0172         return false;
0173     }
0174 
0175     for (const auto& line : d->data) {
0176         if (line.message.isEmpty()) {
0177             continue;
0178         }
0179 
0180         const QString type = msgTypeToString(line.type);
0181         QString output = line.category + " " + type + ": ";
0182         if (!line.file.isEmpty()) {
0183             output += line.file + " ";
0184         }
0185 
0186         if (!line.function.isEmpty()) {
0187             output += line.function + " ";
0188         }
0189 
0190         if (!line.line.isEmpty() && line.line != "0") {
0191             output += line.line + " ";
0192         }
0193 
0194         output += line.message + "\n";
0195         textStream << output;
0196     }
0197 
0198     dump.close();
0199     return true;
0200 }
0201 
0202 void DebugModel::debugMessageHandler(QtMsgType msgType, const QMessageLogContext& context, const QString& message)
0203 {
0204     if (self && Preferences::storeDebugOutput()) {
0205         DebugData d;
0206         d.category = context.category;
0207         d.file = context.file;
0208         d.function = context.function;
0209         d.line = QString::number(context.line);
0210         d.message = message;
0211         d.type = msgType;
0212         d.version = context.version;
0213         self->addData(d);
0214     }
0215 
0216     if (!Preferences::enableDebugOutput()) {
0217         return;
0218     }
0219 
0220     if (message.trimmed().isEmpty()) {
0221         return;
0222     }
0223 
0224     // clang-tidy cert-err33-c requires us to check the return value of printf
0225     auto checkRet = [](int ret) {
0226         assert(ret > 0);
0227         (void) ret;
0228     };
0229 
0230     QString extra;
0231     if (context.file) {
0232         QString file(context.file);
0233         extra = file.split(QDir::separator()).last() + ":" + QString::number(context.line);
0234     }
0235 
0236     QByteArray localMsg = message.toLocal8Bit();
0237     QByteArray extraMsg = extra.toLocal8Bit();
0238     switch (msgType) {
0239     case QtDebugMsg:
0240         checkRet(fprintf(stderr, "Debug: %s %s\n", extraMsg.constData(), localMsg.constData()));
0241         break;
0242     case QtInfoMsg:
0243         checkRet(fprintf(stderr, "Info: %s%s\n", extraMsg.constData(), localMsg.constData()));
0244         break;
0245     case QtWarningMsg:
0246         checkRet(fprintf(stderr, "Warning: %s%s\n", extraMsg.constData(), localMsg.constData()));
0247         break;
0248     case QtCriticalMsg:
0249         checkRet(fprintf(stderr, "Critical: %s%s\n", extraMsg.constData(), localMsg.constData()));
0250         break;
0251     case QtFatalMsg:
0252         checkRet(fprintf(stderr, "Fatal: %s%s\n", extraMsg.constData(), localMsg.constData()));
0253         abort();
0254     }
0255 }
0256 
0257 struct DebugModelFilter::Private {
0258     bool filterDebug = false;
0259     bool filterWarning = false;
0260     bool filterInfo = false;
0261     bool filterCritical = false;
0262     bool filterFatal = false;
0263     bool invertStringFilter = false;
0264     QAbstractItemModel *ignoreCategoriesModel = nullptr;
0265     QString filterString;
0266 };
0267 
0268 DebugModelFilter::DebugModelFilter(): d(std::make_unique<DebugModelFilter::Private>())
0269 {
0270 }
0271 
0272 DebugModelFilter::~DebugModelFilter() = default;
0273 
0274 bool DebugModelFilter::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
0275 {
0276     Q_UNUSED(source_parent);
0277 
0278     auto *currModel = qobject_cast<DebugModel *>(sourceModel());
0279 
0280     // TODO: Less String comparisons;
0281     const QString type =
0282         currModel->data(currModel->index(source_row, DebugModel::Columns::e_TYPE), Qt::DisplayRole).toString();
0283     if (d->filterDebug && type == tr("Debug")) {
0284         return false;
0285     }
0286     if (d->filterCritical && type == tr("Critical")) {
0287         return false;
0288     }
0289     if (d->filterFatal && type == tr("Fatal")) {
0290         return false;
0291     }
0292     if (d->filterInfo && type == tr("Info")) {
0293         return false;
0294     }
0295     if (d->filterWarning && type == tr("Warning")) {
0296         return false;
0297     }
0298 
0299     if (d->ignoreCategoriesModel) {
0300         for (int i = 0, end = d->ignoreCategoriesModel->rowCount(); i < end; i++) {
0301             const QModelIndex ignoreIdx = d->ignoreCategoriesModel->index(i, 0);
0302             const QString categoryToIgnore = d->ignoreCategoriesModel->data(ignoreIdx, Qt::DisplayRole).toString();
0303             const QModelIndex categoryIdx = currModel->index(source_row, DebugModel::e_CATEGORY);
0304             const QString currCategory = currModel->data(categoryIdx, Qt::DisplayRole).toString();
0305 
0306             // TODO: Allow Partial Matches?
0307             if (categoryToIgnore == currCategory) {
0308                 return false;
0309             }
0310         }
0311     }
0312 
0313     if (d->filterString.size()) {
0314         const QModelIndex messageIdx = currModel->index(source_row, DebugModel::Columns::e_MESSAGE);
0315         const QString currMessage = currModel->data(messageIdx, Qt::DisplayRole).toString();
0316         const bool lineContainsFilter = currMessage.contains(d->filterString);
0317 
0318         if (d->invertStringFilter) {
0319             return !lineContainsFilter;
0320         }
0321         return lineContainsFilter;
0322     }
0323 
0324     return true;
0325 }
0326 
0327 bool DebugModelFilter::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
0328 {
0329     Q_UNUSED(source_left);
0330     Q_UNUSED(source_right);
0331     return true;
0332 }
0333 
0334 void DebugModelFilter::setFilterDebug(bool filter)
0335 {
0336     if (d->filterDebug == filter) {
0337         return;
0338     }
0339     d->filterDebug = filter;
0340     invalidateFilter();
0341 }
0342 
0343 void DebugModelFilter::setFilterWarning(bool filter)
0344 {
0345     if (d->filterWarning == filter) {
0346         return;
0347     }
0348     d->filterWarning = filter;
0349     invalidateFilter();
0350 }
0351 
0352 void DebugModelFilter::setFilterInfo(bool filter)
0353 {
0354     if (d->filterInfo == filter) {
0355         return;
0356     }
0357     d->filterInfo = filter;
0358     invalidateFilter();
0359 }
0360 
0361 void DebugModelFilter::setFilterCrtical(bool filter)
0362 {
0363     if (d->filterCritical == filter) {
0364         return;
0365     }
0366     d->filterCritical = filter;
0367     invalidateFilter();
0368 }
0369 
0370 void DebugModelFilter::setFilterFatal(bool filter)
0371 {
0372     if (d->filterFatal == filter) {
0373         return;
0374     }
0375     d->filterFatal = filter;
0376     invalidateFilter();
0377 }
0378 
0379 void DebugModelFilter::setIgnoreCategoriesModel(QAbstractItemModel *model)
0380 {
0381     if (d->ignoreCategoriesModel) {
0382         disconnect(d->ignoreCategoriesModel, &QAbstractItemModel::rowsInserted, this, nullptr);
0383         disconnect(d->ignoreCategoriesModel, &QAbstractItemModel::rowsRemoved, this, nullptr);
0384     }
0385 
0386     d->ignoreCategoriesModel = model;
0387     connect(model, &QAbstractItemModel::rowsInserted, this, [this] {
0388         invalidateFilter();
0389     });
0390 
0391     connect(model, &QAbstractItemModel::rowsRemoved, this, [this] {
0392         invalidateFilter();
0393     });
0394 }
0395 
0396 void DebugModelFilter::setFilterString(const QString& str)
0397 {
0398     if (d->filterString == str) {
0399         return;
0400     }
0401     d->filterString = str;
0402     invalidateFilter();
0403 }
0404 
0405 void DebugModelFilter::setInvertMessageFilter(bool value)
0406 {
0407     if (d->invertStringFilter == value) {
0408         return;
0409     }
0410     d->invertStringFilter = value;
0411     invalidateFilter();
0412 }
0413 
0414 } // namespace Codethink::lvtmdl