File indexing completed on 2024-05-19 05:38:00
0001 /* 0002 SPDX-FileCopyrightText: 2023 Thenujan Sandramohan <sthenujan2002@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "unit.h" 0008 0009 #include <KConfig> 0010 #include <KConfigGroup> 0011 #include <KLocalizedString> 0012 0013 #include <QDBusPendingCall> 0014 #include <QDBusPendingCallWatcher> 0015 #include <QDBusPendingReply> 0016 #include <QDateTime> 0017 #include <QFile> 0018 0019 #if HAVE_SYSTEMD 0020 #include <systemd/sd-journal.h> 0021 #endif 0022 0023 using namespace Qt::Literals::StringLiterals; 0024 0025 static const QMap<QString, QString> STATE_MAP = { 0026 {u"active"_s, i18nc("@label Entry is running right now", "Running")}, 0027 {u"inactive"_s, i18nc("@label Entry is not running right now (exited without error)", "Not running")}, 0028 {u"activating"_s, i18nc("@label Entry is being started", "Starting")}, 0029 {u"deactivating"_s, i18nc("@label Entry is being stopped", "Stopping")}, 0030 {u"failed"_s, i18nc("@label Entry has failed (exited with an error)", "Failed")}, 0031 }; 0032 0033 Unit::Unit(QObject *parent, bool invalid) 0034 : QObject(parent) 0035 { 0036 m_invalid = invalid; 0037 } 0038 0039 Unit::~Unit() 0040 { 0041 } 0042 0043 void Unit::setId(const QString &id) 0044 { 0045 m_id = id; 0046 loadAllProperties(); 0047 } 0048 0049 void Unit::loadAllProperties() 0050 { 0051 // Get unit path using Manager interface 0052 auto message = QDBusMessage::createMethodCall(m_connSystemd, m_pathSysdMgr, m_ifaceMgr, "GetUnit"); 0053 message.setArguments(QList<QVariant>{m_id}); 0054 QDBusPendingCall async = m_sessionBus.asyncCall(message); 0055 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this); 0056 connect(watcher, &QDBusPendingCallWatcher::finished, this, &Unit::callFinishedSlot); 0057 } 0058 0059 void Unit::callFinishedSlot(QDBusPendingCallWatcher *call) 0060 { 0061 QDBusPendingReply<QDBusObjectPath> reply = *call; 0062 if (reply.isError()) { 0063 m_invalid = true; 0064 Q_EMIT dataChanged(); 0065 return; 0066 } else { 0067 m_dbusObjectPath = reply.argumentAt(0).value<QDBusObjectPath>(); 0068 } 0069 call->deleteLater(); 0070 QDBusMessage message = 0071 QDBusMessage::createMethodCall(m_connSystemd, m_dbusObjectPath.path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("GetAll")); 0072 0073 message << m_ifaceUnit; 0074 QDBusPendingCall newCall = m_sessionBus.asyncCall(message); 0075 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(newCall, this); 0076 connect(watcher, &QDBusPendingCallWatcher::finished, this, &Unit::getAllCallback); 0077 } 0078 0079 void Unit::getAllCallback(QDBusPendingCallWatcher *call) 0080 { 0081 QDBusPendingReply<QVariantMap> reply = *call; 0082 if (reply.isError()) { 0083 Q_EMIT error(i18n("Error occurred when receiving reply of GetAll call %1", reply.error().message())); 0084 call->deleteLater(); 0085 return; 0086 } 0087 0088 QVariantMap properties = reply.argumentAt<0>(); 0089 call->deleteLater(); 0090 0091 m_activeState = properties[QStringLiteral("ActiveState")].toString(); 0092 m_activeStateValue = STATE_MAP[m_activeState]; 0093 m_description = properties[QStringLiteral("Description")].toString(); 0094 qulonglong ActiveEnterTimestamp = properties[QStringLiteral("ActiveEnterTimestamp")].toULongLong(); 0095 0096 setActiveEnterTimestamp(ActiveEnterTimestamp); 0097 0098 reloadLogs(); 0099 QDBusConnection userbus = QDBusConnection::connectToBus(QDBusConnection::SessionBus, m_connSystemd); 0100 userbus.connect(m_connSystemd, 0101 m_dbusObjectPath.path(), 0102 "org.freedesktop.DBus.Properties", 0103 "PropertiesChanged", 0104 this, 0105 SLOT(dbusPropertiesChanged(QString, QVariantMap, QStringList))); 0106 } 0107 0108 void Unit::setActiveEnterTimestamp(qulonglong ActiveEnterTimestamp) 0109 { 0110 if (ActiveEnterTimestamp == 0) { 0111 m_timeActivated = QStringLiteral("N/A"); 0112 } else { 0113 QDateTime dateTimeActivated; 0114 dateTimeActivated.setMSecsSinceEpoch(ActiveEnterTimestamp / 1000); 0115 m_timeActivated = dateTimeActivated.toString(); 0116 } 0117 } 0118 0119 void Unit::dbusPropertiesChanged(QString name, QVariantMap map, QStringList list) 0120 { 0121 Q_UNUSED(name); 0122 Q_UNUSED(list); 0123 if (map.contains("ActiveEnterTimestamp")) { 0124 setActiveEnterTimestamp(map["ActiveEnterTimestamp"].toULongLong()); 0125 } 0126 if (map.contains("ActiveState")) { 0127 m_activeState = map["ActiveState"].toString(); 0128 m_activeStateValue = STATE_MAP[m_activeState]; 0129 } 0130 0131 Q_EMIT dataChanged(); 0132 } 0133 0134 void Unit::start() 0135 { 0136 auto message = QDBusMessage::createMethodCall(m_connSystemd, m_dbusObjectPath.path(), m_ifaceUnit, "Start"); 0137 message << QStringLiteral("replace"); 0138 m_sessionBus.send(message); 0139 } 0140 0141 void Unit::stop() 0142 { 0143 auto message = QDBusMessage::createMethodCall(m_connSystemd, m_dbusObjectPath.path(), m_ifaceUnit, "Stop"); 0144 message << QStringLiteral("replace"); 0145 m_sessionBus.send(message); 0146 } 0147 0148 QStringList Unit::getLastJournalEntries(const QString &unit) 0149 { 0150 #if HAVE_SYSTEMD 0151 sd_journal *journal; 0152 0153 int returnValue = sd_journal_open(&journal, (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_CURRENT_USER)); 0154 if (returnValue != 0) { 0155 Q_EMIT journalError(i18n("Failed to open journal")); 0156 sd_journal_close(journal); 0157 return {}; 0158 } 0159 0160 sd_journal_flush_matches(journal); 0161 0162 const QString match1 = QStringLiteral("USER_UNIT=%1").arg(unit); 0163 returnValue = sd_journal_add_match(journal, match1.toUtf8(), 0); 0164 if (returnValue != 0) { 0165 sd_journal_close(journal); 0166 return {}; 0167 } 0168 0169 sd_journal_add_disjunction(journal); 0170 0171 const QString match2 = QStringLiteral("_SYSTEMD_USER_UNIT=%1").arg(unit); 0172 returnValue = sd_journal_add_match(journal, match2.toUtf8(), 0); 0173 if (returnValue != 0) { 0174 sd_journal_close(journal); 0175 return {}; 0176 } 0177 0178 returnValue = sd_journal_seek_tail(journal); 0179 if (returnValue != 0) { 0180 sd_journal_close(journal); 0181 return {}; 0182 } 0183 0184 QStringList reply; 0185 QString lastDateTime; 0186 // Fetch the last 50 entries 0187 for (int i = 0; i < 50; ++i) { 0188 returnValue = sd_journal_previous(journal); 0189 if (returnValue != 1) { 0190 // previous failed, no more entries 0191 sd_journal_close(journal); 0192 return reply; 0193 } 0194 QString line; 0195 0196 // Get the date and time 0197 uint64_t time; 0198 returnValue = sd_journal_get_realtime_usec(journal, &time); 0199 0200 if (returnValue == 0) { 0201 QDateTime date; 0202 date.setMSecsSinceEpoch(time / 1000); 0203 if (lastDateTime != date.toString()) { 0204 line.append(date.toString("yyyy.MM.dd hh:mm: ")); 0205 lastDateTime = date.toString(); 0206 } else { 0207 line.append(QString("​ ").repeated(33)); 0208 } 0209 } 0210 0211 // Color messages according to priority 0212 size_t length; 0213 const void *data; 0214 returnValue = sd_journal_get_data(journal, "PRIORITY", &data, &length); 0215 if (returnValue == 0) { 0216 int prio = QString::fromUtf8((const char *)data, length).section('=', 1).toInt(); 0217 // Adding color to the logs is done in c++ so we can't add Kirigami color here so we add a string here 0218 // and then replace it all with actual colors in qml code 0219 if (prio <= 3) 0220 line.append("<font color='Kirigami.Theme.negativeTextColor'>"); 0221 else if (prio == 4) 0222 line.append("<font color='Kirigami.Theme.neutralTextColor'>"); 0223 else 0224 line.append("<font>"); 0225 } 0226 0227 // Get the message itself 0228 returnValue = sd_journal_get_data(journal, "MESSAGE", &data, &length); 0229 if (returnValue == 0) { 0230 line.append(QString::fromUtf8((const char *)data, length).section('=', 1) + "</font>"); 0231 reply << line; 0232 } 0233 } 0234 0235 sd_journal_close(journal); 0236 0237 return reply; 0238 #else 0239 return QStringList(); 0240 #endif 0241 } 0242 0243 void Unit::reloadLogs() 0244 { 0245 QStringList logsList = getLastJournalEntries(m_id); 0246 0247 logsList.removeAll({}); 0248 m_logs = logsList.join(QStringLiteral("<br>")); 0249 0250 Q_EMIT dataChanged(); 0251 }