File indexing completed on 2023-10-01 09:31:08

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 "journalduniquequerymodel.h"
0007 #include "journalduniquequerymodel_p.h"
0008 #include "kjournald_export.h"
0009 #include "kjournaldlib_log_general.h"
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QString>
0013 #include <memory>
0014 
0015 JournaldUniqueQueryModelPrivate::~JournaldUniqueQueryModelPrivate()
0016 {
0017     sd_journal_close(mJournal);
0018     mJournal = nullptr;
0019 }
0020 
0021 void JournaldUniqueQueryModelPrivate::closeJournal()
0022 {
0023     if (mJournal) {
0024         sd_journal_close(mJournal);
0025         mJournal = nullptr;
0026     }
0027 }
0028 
0029 bool JournaldUniqueQueryModelPrivate::openJournal()
0030 {
0031     closeJournal();
0032     // TODO allow custom selection of journal type
0033     int result = sd_journal_open(&mJournal, SD_JOURNAL_LOCAL_ONLY);
0034     if (result < 0) {
0035         qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result);
0036         return false;
0037     }
0038     return true;
0039 }
0040 
0041 bool JournaldUniqueQueryModelPrivate::openJournalFromPath(const QString &path)
0042 {
0043     closeJournal();
0044     if (path.isEmpty() || !QDir().exists(path)) {
0045         qCCritical(KJOURNALDLIB_GENERAL) << "Journal directory does not exist, abort opening";
0046         return false;
0047     }
0048     const QFileInfo fileInfo = QFileInfo(path);
0049     if (fileInfo.isDir()) {
0050         int result = sd_journal_open_directory(&mJournal, path.toStdString().c_str(), 0 /* no flags, directory defines type */);
0051         if (result < 0) {
0052             qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result);
0053             return false;
0054         }
0055     } else if (fileInfo.isFile()) {
0056         const char **files = new const char *[1];
0057         QByteArray journalPath = path.toLocal8Bit();
0058         files[0] = journalPath.data();
0059 
0060         int result = sd_journal_open_files(&mJournal, files, 0 /* no flags, directory defines type */);
0061         delete[] files;
0062         if (result < 0) {
0063             qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result);
0064             return false;
0065         }
0066     }
0067 
0068     return true;
0069 }
0070 
0071 void JournaldUniqueQueryModelPrivate::runQuery()
0072 {
0073     if (!mJournal || mFieldString.isEmpty()) {
0074         return;
0075     }
0076     mEntries.clear();
0077 
0078     QVector<std::pair<QString, bool>> dataList;
0079     const void *data;
0080     size_t length;
0081     int result = sd_journal_query_unique(mJournal, mFieldString.toStdString().c_str());
0082     if (result < 0) {
0083         qCritical() << "Failed to query journal:" << strerror(-result);
0084         return;
0085     }
0086     const int fieldLength = mFieldString.length() + 1;
0087     SD_JOURNAL_FOREACH_UNIQUE(mJournal, data, length)
0088     {
0089         QString dataStr = QString::fromLocal8Bit(static_cast<const char *>(data));
0090         dataStr = dataStr.remove(0, fieldLength);
0091         if (dataStr.endsWith(QLatin1String("\u0001"))) {
0092             dataStr = dataStr.left(dataStr.length() - QString(QLatin1String("\u0001")).length());
0093         }
0094         if (dataStr.endsWith(QLatin1String("\u0002"))) {
0095             dataStr = dataStr.left(dataStr.length() - QString(QLatin1String("\u0002")).length());
0096         }
0097         dataStr = JournaldHelper::cleanupString(dataStr);
0098         dataList << std::pair<QString, bool>{dataStr, true};
0099     }
0100 
0101     mEntries = dataList;
0102 }
0103 
0104 JournaldUniqueQueryModel::JournaldUniqueQueryModel(QObject *parent)
0105     : QAbstractItemModel(parent)
0106     , d(new JournaldUniqueQueryModelPrivate)
0107 {
0108     d->openJournal();
0109     d->runQuery();
0110 }
0111 
0112 JournaldUniqueQueryModel::JournaldUniqueQueryModel(const QString &journalPath, QObject *parent)
0113     : QAbstractItemModel(parent)
0114     , d(new JournaldUniqueQueryModelPrivate)
0115 {
0116     d->openJournalFromPath(journalPath);
0117     d->runQuery();
0118 }
0119 
0120 JournaldUniqueQueryModel::~JournaldUniqueQueryModel() = default;
0121 
0122 bool JournaldUniqueQueryModel::setJournaldPath(const QString &path)
0123 {
0124     bool success{true};
0125     beginResetModel();
0126     success = d->openJournalFromPath(path);
0127     if (success) {
0128         d->runQuery();
0129     }
0130     endResetModel();
0131     return success;
0132 }
0133 
0134 void JournaldUniqueQueryModel::setField(JournaldHelper::Field field)
0135 {
0136     setFieldString(JournaldHelper::mapField(field));
0137 }
0138 
0139 void JournaldUniqueQueryModel::setFieldString(const QString &fieldString)
0140 {
0141     beginResetModel();
0142     d->mFieldString = fieldString;
0143     d->runQuery();
0144     endResetModel();
0145 }
0146 
0147 QString JournaldUniqueQueryModel::fieldString() const
0148 {
0149     return d->mFieldString;
0150 }
0151 
0152 QHash<int, QByteArray> JournaldUniqueQueryModel::roleNames() const
0153 {
0154     QHash<int, QByteArray> roles;
0155     roles[JournaldUniqueQueryModel::FIELD] = "field";
0156     roles[JournaldUniqueQueryModel::SELECTED] = "selected";
0157     return roles;
0158 }
0159 
0160 void JournaldUniqueQueryModel::setSystemJournal()
0161 {
0162     beginResetModel();
0163     d->openJournal();
0164     endResetModel();
0165 }
0166 
0167 QModelIndex JournaldUniqueQueryModel::index(int row, int column, const QModelIndex &parent) const
0168 {
0169     return createIndex(row, column);
0170 }
0171 
0172 QModelIndex JournaldUniqueQueryModel::parent(const QModelIndex &index) const
0173 {
0174     // no tree model, thus no parent
0175     return QModelIndex();
0176 }
0177 
0178 int JournaldUniqueQueryModel::rowCount(const QModelIndex &parent) const
0179 {
0180     // model represents a list and has has no children
0181     if (!parent.isValid()) {
0182         return d->mEntries.size();
0183     } else {
0184         return 0;
0185     }
0186 }
0187 
0188 int JournaldUniqueQueryModel::columnCount(const QModelIndex &parent) const
0189 {
0190     return 1;
0191 }
0192 
0193 QVariant JournaldUniqueQueryModel::data(const QModelIndex &index, int role) const
0194 {
0195     if (d->mEntries.count() <= index.row()) {
0196         return QVariant();
0197     }
0198     switch (role) {
0199     case Qt::DisplayRole:
0200         Q_FALLTHROUGH();
0201     case JournaldUniqueQueryModel::Roles::FIELD:
0202         return d->mEntries.at(index.row()).first;
0203     case JournaldUniqueQueryModel::Roles::SELECTED:
0204         return QVariant::fromValue<bool>(d->mEntries.at(index.row()).second);
0205     }
0206     return QVariant();
0207 }
0208 
0209 bool JournaldUniqueQueryModel::setData(const QModelIndex &index, const QVariant &value, int role)
0210 {
0211     if (d->mEntries.count() <= index.row()) {
0212         return false;
0213     }
0214     if (role == JournaldUniqueQueryModel::Roles::SELECTED) {
0215         if (d->mEntries.at(index.row()).second == value.toBool()) {
0216             return false;
0217         } else {
0218             d->mEntries[index.row()].second = value.toBool();
0219             Q_EMIT dataChanged(index, index);
0220             return true;
0221         }
0222     }
0223     return QAbstractItemModel::setData(index, value, role);
0224 }