Warning, file /system/kjournald/lib/filtercriteriamodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-License-Identifier: LGPL-2.1-or-later OR MIT
0003     SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr <cordlandwehr@kde.org>
0004 */
0005 
0006 #include "filtercriteriamodel.h"
0007 #include "filtercriteriamodel_p.h"
0008 #include "kjournald_export.h"
0009 #include "kjournaldlib_log_general.h"
0010 #include <KLocalizedString>
0011 #include <QDebug>
0012 #include <QDir>
0013 #include <QString>
0014 #include <memory>
0015 
0016 constexpr quint8 sDefaultPriorityLevel{5};
0017 
0018 QString FilterCriteriaModelPrivate::mapPriorityToString(int priority)
0019 {
0020     switch (priority) {
0021     case 0:
0022         return i18nc("Radio box option, log priority value", "Emergency");
0023     case 1:
0024         return i18nc("Radio box option, log priority value", "Alert");
0025     case 2:
0026         return i18nc("Radio box option, log priority value", "Critical");
0027     case 3:
0028         return i18nc("Radio box option, log priority value", "Error");
0029     case 4:
0030         return i18nc("Radio box option, log priority value", "Warning");
0031     case 5:
0032         return i18nc("Radio box option, log priority value", "Notice");
0033     case 6:
0034         return i18nc("Radio box option, log priority value", "Info");
0035     case 7:
0036         return i18nc("Radio box option, log priority value", "Debug");
0037     case -1:
0038         return i18nc("Radio box option, log priority value", "No Filter");
0039     }
0040     return QLatin1String("");
0041 }
0042 
0043 SelectionEntry::SelectionEntry(const QString &text,
0044                                const QVariant &data,
0045                                FilterCriteriaModel::Category category,
0046                                bool selected,
0047                                std::shared_ptr<SelectionEntry> parent)
0048     : mText(text)
0049     , mData(data)
0050     , mCategory(category)
0051     , mSelected(selected)
0052     , mParentItem(parent)
0053 {
0054 }
0055 
0056 void SelectionEntry::appendChild(std::shared_ptr<SelectionEntry> item)
0057 {
0058     mChildItems.push_back(item);
0059 }
0060 
0061 std::shared_ptr<SelectionEntry> SelectionEntry::child(int row)
0062 {
0063     if (row < 0 || row >= mChildItems.size()) {
0064         return nullptr;
0065     }
0066     return mChildItems.at(row);
0067 }
0068 
0069 int SelectionEntry::childCount() const
0070 {
0071     return mChildItems.size();
0072 }
0073 
0074 int SelectionEntry::row() const
0075 {
0076     auto parent = mParentItem.lock();
0077     if (parent) {
0078         for (int i = 0; i < parent->mChildItems.size(); ++i) {
0079             if (parent->mChildItems.at(i).get() == this) {
0080                 return i;
0081             }
0082         }
0083     }
0084     return 0;
0085 }
0086 
0087 int SelectionEntry::columnCount() const
0088 {
0089     return 1;
0090 }
0091 
0092 QVariant SelectionEntry::data(FilterCriteriaModel::Roles role) const
0093 {
0094     switch (role) {
0095     case FilterCriteriaModel::Roles::CATEGORY:
0096         return QVariant::fromValue(mCategory);
0097     case FilterCriteriaModel::Roles::TEXT:
0098         return QVariant::fromValue(mText); // TODO abbreviate
0099     case FilterCriteriaModel::Roles::DATA:
0100         return QVariant::fromValue(mData);
0101     case FilterCriteriaModel::Roles::LONGTEXT:
0102         return QVariant::fromValue(mText);
0103     case FilterCriteriaModel::Roles::SELECTED:
0104         return QVariant::fromValue(mSelected);
0105     }
0106     return QVariant();
0107 }
0108 
0109 bool SelectionEntry::setData(const QVariant &value, FilterCriteriaModel::Roles role)
0110 {
0111     if (role == FilterCriteriaModel::Roles::SELECTED) {
0112         mSelected = value.toBool();
0113         return true;
0114     }
0115     qCWarning(KJOURNALDLIB_GENERAL) << "no settable role";
0116     return false;
0117 }
0118 
0119 std::shared_ptr<SelectionEntry> SelectionEntry::parentItem()
0120 {
0121     return mParentItem.lock();
0122 }
0123 
0124 FilterCriteriaModelPrivate::FilterCriteriaModelPrivate()
0125 {
0126     rebuildModel();
0127 }
0128 
0129 FilterCriteriaModelPrivate::~FilterCriteriaModelPrivate() = default;
0130 
0131 void FilterCriteriaModelPrivate::rebuildModel()
0132 {
0133     mRootItem = std::make_unique<SelectionEntry>();
0134     {
0135         auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for log message source", "Transport"),
0136                                                        QVariant(),
0137                                                        FilterCriteriaModel::Category::TRANSPORT,
0138                                                        false,
0139                                                        mRootItem);
0140         mRootItem->appendChild(parent);
0141         mRootItem->child(FilterCriteriaModel::Category::TRANSPORT)
0142             ->appendChild(std::move(std::make_unique<SelectionEntry>(i18nc("Checkbox option for kernel log messages", "Kernel"),
0143                                                                      QLatin1String("kernel"),
0144                                                                      FilterCriteriaModel::Category::TRANSPORT,
0145                                                                      false,
0146                                                                      parent)));
0147     }
0148     {
0149         auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for log message priority", "Priority"),
0150                                                        QVariant(),
0151                                                        FilterCriteriaModel::Category::PRIORITY,
0152                                                        false,
0153                                                        mRootItem);
0154         mRootItem->appendChild(parent);
0155         for (int i = 0; i <= 7; ++i) {
0156             mRootItem->child(FilterCriteriaModel::Category::PRIORITY)
0157                 ->appendChild(std::move(std::make_unique<SelectionEntry>(mapPriorityToString(i),
0158                                                                          QString::number(i),
0159                                                                          FilterCriteriaModel::Category::PRIORITY,
0160                                                                          i == sDefaultPriorityLevel ? true : false,
0161                                                                          parent)));
0162         }
0163         // add "no filter" option at end
0164         mRootItem->child(FilterCriteriaModel::Category::PRIORITY)
0165             ->appendChild(std::move(
0166                 std::make_unique<SelectionEntry>(mapPriorityToString(-1), QString::number(-1), FilterCriteriaModel::Category::PRIORITY, false, parent)));
0167         mPriorityLevel = sDefaultPriorityLevel;
0168     }
0169     {
0170         auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for systemd unit", "Unit"),
0171                                                        QVariant(),
0172                                                        FilterCriteriaModel::Category::SYSTEMD_UNIT,
0173                                                        false,
0174                                                        mRootItem);
0175         mRootItem->appendChild(parent);
0176         QVector<QString> units = JournaldHelper::queryUnique(mJournal, JournaldHelper::Field::_SYSTEMD_UNIT);
0177         std::sort(std::begin(units), std::end(units), [](const QString &a, const QString &b) {
0178             return QString::compare(a, b, Qt::CaseInsensitive) <= 0;
0179         });
0180         for (const auto &unit : std::as_const(units)) {
0181             // skip any non-service units, because we expect users only be interested in filtering those
0182             if (!unit.endsWith(QLatin1String(".service"))) {
0183                 continue;
0184             }
0185             mRootItem->child(FilterCriteriaModel::Category::SYSTEMD_UNIT)
0186                 ->appendChild(std::move(
0187                     std::make_unique<SelectionEntry>(JournaldHelper::cleanupString(unit), unit, FilterCriteriaModel::Category::SYSTEMD_UNIT, false, parent)));
0188         }
0189     }
0190     {
0191         auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for process list", "Process"),
0192                                                        QVariant(),
0193                                                        FilterCriteriaModel::Category::EXE,
0194                                                        false,
0195                                                        mRootItem);
0196         mRootItem->appendChild(parent);
0197         QVector<QString> exes = JournaldHelper::queryUnique(mJournal, JournaldHelper::Field::_EXE);
0198         std::sort(std::begin(exes), std::end(exes), [](const QString &a, const QString &b) {
0199             return QString::compare(a, b, Qt::CaseInsensitive) <= 0;
0200         });
0201         for (const auto &exe : std::as_const(exes)) {
0202             mRootItem->child(FilterCriteriaModel::Category::EXE)
0203                 ->appendChild(
0204                     std::move(std::make_unique<SelectionEntry>(JournaldHelper::cleanupString(exe), exe, FilterCriteriaModel::Category::EXE, false, parent)));
0205         }
0206     }
0207 }
0208 
0209 FilterCriteriaModel::FilterCriteriaModel(QObject *parent)
0210     : QAbstractItemModel(parent)
0211     , d(new FilterCriteriaModelPrivate)
0212 {
0213     beginResetModel();
0214     d->mJournal = std::make_shared<LocalJournal>();
0215     d->rebuildModel();
0216     endResetModel();
0217 }
0218 
0219 FilterCriteriaModel::FilterCriteriaModel(const QString &journalPath, QObject *parent)
0220     : QAbstractItemModel(parent)
0221     , d(new FilterCriteriaModelPrivate)
0222 {
0223     beginResetModel();
0224     d->mJournal = std::make_shared<LocalJournal>(journalPath);
0225     d->rebuildModel();
0226     endResetModel();
0227 }
0228 
0229 FilterCriteriaModel::~FilterCriteriaModel() = default;
0230 
0231 bool FilterCriteriaModel::setJournaldPath(const QString &path)
0232 {
0233     beginResetModel();
0234     d->mJournal = std::make_shared<LocalJournal>(path);
0235     bool success = d->mJournal->isValid();
0236     d->rebuildModel();
0237     endResetModel();
0238     return success;
0239 }
0240 
0241 QHash<int, QByteArray> FilterCriteriaModel::roleNames() const
0242 {
0243     QHash<int, QByteArray> roles;
0244     roles[FilterCriteriaModel::TEXT] = "text";
0245     roles[FilterCriteriaModel::DATA] = "data";
0246     roles[FilterCriteriaModel::LONGTEXT] = "longtext";
0247     roles[FilterCriteriaModel::CATEGORY] = "category";
0248     roles[FilterCriteriaModel::SELECTED] = "selected";
0249     return roles;
0250 }
0251 
0252 void FilterCriteriaModel::setSystemJournal()
0253 {
0254     beginResetModel();
0255     d->mJournal = std::make_shared<LocalJournal>();
0256     d->rebuildModel();
0257     endResetModel();
0258 }
0259 
0260 int FilterCriteriaModel::priorityFilter() const
0261 {
0262     return static_cast<qint8>(d->mPriorityLevel.value_or(-1));
0263 }
0264 
0265 QStringList FilterCriteriaModel::systemdUnitFilter() const
0266 {
0267     std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(FilterCriteriaModel::Category::SYSTEMD_UNIT);
0268     QStringList entries;
0269     for (int i = 0; i < parent->childCount(); ++i) {
0270         if (parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
0271             entries.append(parent->child(i)->data(FilterCriteriaModel::DATA).toString());
0272         }
0273     }
0274     return entries;
0275 }
0276 
0277 QStringList FilterCriteriaModel::exeFilter() const
0278 {
0279     std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(FilterCriteriaModel::Category::EXE);
0280     QStringList entries;
0281     for (int i = 0; i < parent->childCount(); ++i) {
0282         if (parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
0283             entries.append(parent->child(i)->data(FilterCriteriaModel::DATA).toString());
0284         }
0285     }
0286     return entries;
0287 }
0288 
0289 bool FilterCriteriaModel::isKernelFilterEnabled() const
0290 {
0291     std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(FilterCriteriaModel::Category::TRANSPORT);
0292     for (int i = 0; i < parent->childCount(); ++i) {
0293         if (parent->child(i)->data(FilterCriteriaModel::DATA) == QLatin1String("kernel") && parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
0294             return true;
0295         }
0296     }
0297     return false;
0298 }
0299 
0300 QModelIndex FilterCriteriaModel::index(int row, int column, const QModelIndex &parent) const
0301 {
0302     if (!hasIndex(row, column, parent))
0303         return QModelIndex();
0304 
0305     SelectionEntry *parentItem;
0306 
0307     if (!parent.isValid()) {
0308         parentItem = d->mRootItem.get();
0309     } else {
0310         parentItem = static_cast<SelectionEntry *>(parent.internalPointer());
0311     }
0312 
0313     std::shared_ptr<SelectionEntry> childItem = parentItem->child(row);
0314     if (childItem) {
0315         return createIndex(row, column, childItem.get());
0316     }
0317     return QModelIndex();
0318 }
0319 
0320 QModelIndex FilterCriteriaModel::parent(const QModelIndex &index) const
0321 {
0322     if (!index.isValid()) {
0323         return QModelIndex();
0324     }
0325 
0326     SelectionEntry *childItem = static_cast<SelectionEntry *>(index.internalPointer());
0327     std::shared_ptr<SelectionEntry> parentItem = childItem->parentItem();
0328 
0329     if (parentItem.get() == d->mRootItem.get()) {
0330         return QModelIndex();
0331     }
0332 
0333     return createIndex(parentItem->row(), 0, parentItem.get());
0334 }
0335 
0336 int FilterCriteriaModel::rowCount(const QModelIndex &parent) const
0337 {
0338     if (!parent.isValid()) {
0339         return d->mRootItem->childCount();
0340     } else {
0341         auto parentItem = static_cast<SelectionEntry *>(parent.internalPointer());
0342         return parentItem->childCount();
0343     }
0344     return 0;
0345 }
0346 
0347 int FilterCriteriaModel::columnCount(const QModelIndex &parent) const
0348 {
0349     return 1;
0350 }
0351 
0352 QVariant FilterCriteriaModel::data(const QModelIndex &index, int role) const
0353 {
0354     if (!index.parent().isValid()) {
0355         if (index.row() < 0 || index.row() >= d->mRootItem->childCount()) {
0356             qCCritical(KJOURNALDLIB_GENERAL) << "Index out of range" << index;
0357             return QVariant();
0358         }
0359         return d->mRootItem->child(index.row())->data(static_cast<FilterCriteriaModel::Roles>(role));
0360     } else {
0361         auto entry = static_cast<SelectionEntry *>(index.internalPointer());
0362         if (nullptr != entry) {
0363             return entry->data(static_cast<FilterCriteriaModel::Roles>(role));
0364         }
0365     }
0366     return QVariant();
0367 }
0368 
0369 bool FilterCriteriaModel::setData(const QModelIndex &index, const QVariant &value, int role)
0370 {
0371     auto entry = static_cast<SelectionEntry *>(index.internalPointer());
0372     if (nullptr == entry) {
0373         return QAbstractItemModel::setData(index, value, role);
0374     }
0375     if (value == entry->data(static_cast<FilterCriteriaModel::Roles>(role))) {
0376         return false; // nothing to do
0377     }
0378 
0379     // clear operations for top-level categories
0380     const bool result = entry->setData(value, static_cast<FilterCriteriaModel::Roles>(role));
0381     const auto category = entry->data(FilterCriteriaModel::Roles::CATEGORY).value<FilterCriteriaModel::Category>();
0382     Q_EMIT dataChanged(index, index, {role});
0383 
0384     if (result && category == FilterCriteriaModel::Category::PRIORITY && static_cast<FilterCriteriaModel::Roles>(role) == SELECTED) {
0385         // only listen on changes that set entry data to true, because this is considered a selector in the list
0386         std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(FilterCriteriaModel::Category::PRIORITY);
0387         for (int i = 0; i < parent->childCount(); ++i) {
0388             const bool selectedValue = (i == index.row());
0389             parent->child(i)->setData(selectedValue, FilterCriteriaModel::SELECTED);
0390             static_cast<SelectionEntry *>(FilterCriteriaModel::index(i, 0, index.parent()).internalPointer())
0391                 ->setData(selectedValue, static_cast<FilterCriteriaModel::Roles>(role));
0392         }
0393         Q_EMIT dataChanged(FilterCriteriaModel::index(0, 0, index.parent()), FilterCriteriaModel::index(parent->childCount() - 1, 0, index.parent()), {role});
0394         Q_ASSERT(index.row() >= 0);
0395         if (parent->child(index.row())->data(FilterCriteriaModel::DATA).toInt() >= 0) {
0396             d->mPriorityLevel = parent->child(index.row())->data(FilterCriteriaModel::DATA).toInt();
0397         } else {
0398             d->mPriorityLevel = std::nullopt;
0399         }
0400         qCDebug(KJOURNALDLIB_GENERAL) << "set priority level to:" << static_cast<qint8>(d->mPriorityLevel.value_or(-1));
0401         Q_EMIT priorityFilterChanged(index.row());
0402     } else if (result && category == FilterCriteriaModel::Category::SYSTEMD_UNIT) {
0403         // for checkable entries update parent's selected state
0404         if (value.toBool() == true) {
0405             setData(index.parent(), true, FilterCriteriaModel::Roles::SELECTED);
0406         } else {
0407             const auto parent = static_cast<SelectionEntry *>(index.parent().internalPointer());
0408             if (parent) {
0409                 bool hasSelectedSibling{false};
0410                 for (int i = 0; i < parent->childCount(); ++i) {
0411                     hasSelectedSibling = hasSelectedSibling || parent->child(i)->data(SELECTED).toBool();
0412                 }
0413                 setData(index.parent(), hasSelectedSibling, FilterCriteriaModel::Roles::SELECTED);
0414             }
0415         }
0416         Q_EMIT systemdUnitFilterChanged();
0417     } else if (result && category == FilterCriteriaModel::Category::EXE) {
0418         // for checkable entries update parent's selected state
0419         if (value.toBool() == true) {
0420             setData(index.parent(), true, FilterCriteriaModel::Roles::SELECTED);
0421         } else {
0422             const auto parent = static_cast<SelectionEntry *>(index.parent().internalPointer());
0423             if (parent) {
0424                 bool hasSelectedSibling{false};
0425                 for (int i = 0; i < parent->childCount(); ++i) {
0426                     hasSelectedSibling = hasSelectedSibling || parent->child(i)->data(SELECTED).toBool();
0427                 }
0428                 setData(index.parent(), hasSelectedSibling, FilterCriteriaModel::Roles::SELECTED);
0429             }
0430         }
0431         Q_EMIT exeFilterChanged();
0432     } else if (result && category == FilterCriteriaModel::Category::TRANSPORT) {
0433         Q_EMIT kernelFilterChanged();
0434     }
0435     return result;
0436 }
0437 
0438 QVector<std::pair<QString, bool>> FilterCriteriaModel::entries(FilterCriteriaModel::Category category) const
0439 {
0440     QVector<std::pair<QString, bool>> values;
0441 
0442     for (int i = 0; i < d->mRootItem->child(static_cast<int>(category))->childCount(); ++i) {
0443         values.append(
0444             std::make_pair<QString, bool>(d->mRootItem->child(static_cast<int>(category))->child(i)->data(FilterCriteriaModel::DATA).toString(), false));
0445     }
0446     return values;
0447 }