File indexing completed on 2024-05-19 05:49:20
0001 /*************************************************************************** 0002 * Copyright © 2009 Jonathan Thomas <echidnaman@kubuntu.org> * 0003 * Copyright © 2009 Amichai Rothman <amichai2@amichais.net> * 0004 * Copyright © 2014-2015 Harald Sitter <sitter@kde.org> * 0005 * * 0006 * This program is free software; you can redistribute it and/or * 0007 * modify it under the terms of the GNU General Public License as * 0008 * published by the Free Software Foundation; either version 2 of * 0009 * the License or (at your option) version 3 or any later version * 0010 * accepted by the membership of KDE e.V. (or its successor approved * 0011 * by the membership of KDE e.V.), which shall act as a proxy * 0012 * defined in Section 14 of version 3 of the license. * 0013 * * 0014 * This program is distributed in the hope that it will be useful, * 0015 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0017 * GNU General Public License for more details. * 0018 * * 0019 * You should have received a copy of the GNU General Public License * 0020 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0021 ***************************************************************************/ 0022 0023 #include "hook.h" 0024 0025 // Qt includes 0026 #include <QCryptographicHash> 0027 #include <QDir> 0028 #include <QFileInfo> 0029 #include <QTextStream> 0030 #include <QDateTime> 0031 #include <QStringBuilder> 0032 0033 // KDE includes 0034 #include <KProcess> 0035 #include <KToolInvocation> 0036 #include <KConfig> 0037 #include <KConfigGroup> 0038 0039 #include "locale.h" 0040 0041 float getUptime() 0042 { 0043 float uptime = 0; 0044 QFile uptimeFile("/proc/uptime"); 0045 if (uptimeFile.open(QFile::ReadOnly)) { 0046 QTextStream stream(&uptimeFile); 0047 QString uptimeLine = stream.readLine(); 0048 QStringList uptimeStringList = uptimeLine.split(' '); 0049 // We don't need the last part of /proc/uptime 0050 uptimeStringList.removeLast(); 0051 QString uptimeString = uptimeStringList.first(); 0052 uptime = uptimeString.toFloat(); 0053 } 0054 return uptime; 0055 } 0056 0057 QString trimLeft(QString str, int start = 0) 0058 { 0059 int len = str.length(); 0060 while (start < len && str[start].isSpace()) 0061 start++; 0062 return str.mid(start); 0063 } 0064 0065 Hook::Hook(QObject *parent, const QString &hookPath) 0066 : QObject(parent) 0067 , m_hookPath(hookPath) 0068 , m_finished(false) 0069 , m_locale(QLatin1String(setlocale(LC_ALL, NULL))) 0070 { 0071 m_fields = parse(hookPath); 0072 loadConfig(); 0073 } 0074 0075 Hook::~Hook() 0076 {} 0077 0078 QString Hook::locale() 0079 { 0080 return m_locale; 0081 } 0082 0083 void Hook::setLocale(const QString &locale) 0084 { 0085 m_locale = locale; 0086 } 0087 0088 QString Hook::getField(const QString &name) const 0089 { 0090 // Try to lookup the field with -LOCALE appended, then -LANGUAGE then without 0091 // suffix. 0092 Locale locale(m_locale); 0093 QString prefix = name % QChar('-'); 0094 QString value; 0095 for (QString locale_combo: locale.combinations()) { 0096 value = m_fields.value(prefix + locale_combo); 0097 if (!value.isEmpty()) { 0098 break; 0099 } 0100 } 0101 if (value.isEmpty()) { 0102 value = m_fields.value(name); 0103 } 0104 return value; 0105 } 0106 0107 bool Hook::isValid() const 0108 { 0109 return !m_fields.isEmpty(); 0110 } 0111 0112 void Hook::runCommand() 0113 { 0114 QString command = getField("Command"); 0115 if (getField("Terminal") == "True") { 0116 // if command is quoted, invokeTerminal will refuse to interpret it properly 0117 if (command.startsWith('\"') && command.endsWith('\"')) { 0118 command = command.mid(1, command.length() - 2); 0119 } 0120 KToolInvocation::invokeTerminal(command); 0121 } else { 0122 KProcess process; 0123 process.setShellCommand(command); 0124 process.startDetached(); 0125 } 0126 } 0127 0128 void Hook::setFinished() 0129 { 0130 m_finished = true; 0131 saveConfig(); 0132 } 0133 0134 void Hook::loadConfig() 0135 { 0136 QString signature = calculateSignature(); 0137 KConfig config("notificationhelper", KConfig::NoGlobals); 0138 KConfigGroup group(&config, "updateNotifications"); 0139 0140 m_finished = group.readEntry(signature, false); 0141 0142 // remain backward compatibile with update-notifier-kde 0143 // so that after upgrade old notifications are not resurrected 0144 if (!m_finished) { 0145 KConfig oldconfig("update-notifier-kderc", KConfig::NoGlobals); 0146 KConfigGroup oldgroup(&oldconfig, "updateNotifications"); 0147 QFileInfo fileinfo(m_hookPath); 0148 QString oldsignature = fileinfo.fileName(); 0149 m_finished = oldgroup.readEntry(oldsignature, false); 0150 if (m_finished) 0151 saveConfig(); // copy over to new configuration 0152 } 0153 } 0154 0155 void Hook::saveConfig() 0156 { 0157 QString signature = calculateSignature(); 0158 KConfig config("notificationhelper", KConfig::NoGlobals); 0159 KConfigGroup group(&config, "updateNotifications"); 0160 0161 group.writeEntry(signature, m_finished); 0162 group.sync(); 0163 } 0164 0165 QString Hook::calculateSignature() const 0166 { 0167 // this is used to uniquely identify a hook so that 0168 // it is not shown again after it has been executed 0169 QFile file(m_hookPath); 0170 QFileInfo fileinfo(m_hookPath); 0171 QString timestamp = fileinfo.lastModified().toString(Qt::ISODate); 0172 QString filename = fileinfo.fileName(); 0173 0174 QCryptographicHash hash(QCryptographicHash::Md5); 0175 hash.addData(filename.toUtf8()); 0176 hash.addData(timestamp.toUtf8()); 0177 hash.addData(&file); 0178 return hash.result(); 0179 } 0180 0181 QMap<QString, QString> Hook::parse(const QString &hookPath) 0182 { 0183 const QMap<QString, QString> emptyMap; 0184 0185 QFile file(hookPath); 0186 if (!file.open(QFile::ReadOnly)) { 0187 return emptyMap; 0188 } 0189 0190 // See https://wiki.kubuntu.org/InteractiveUpgradeHooks for details on the hook format 0191 QMap<QString, QString> fields; 0192 QTextStream stream(&file); 0193 stream.setCodec("UTF-8"); // as required by spec 0194 stream.setAutoDetectUnicode(true); // just in case 0195 0196 QString lastKey; 0197 QString line; 0198 do { 0199 line = stream.readLine(); 0200 if (line.isEmpty()) { 0201 continue; // skip empty lines, e.g. at end of file 0202 } else if (line.at(0).isSpace()) { 0203 line = trimLeft(line); 0204 if (line.isEmpty()) 0205 continue; // treat it like empty line (lenient) 0206 if (lastKey.isEmpty()) 0207 return emptyMap; // not a valid upgrade hook 0208 QString value = fields[lastKey]; 0209 if (!value.isEmpty()) 0210 value += ' '; 0211 fields[lastKey] = value + line; 0212 } else { 0213 int split = line.indexOf(':'); 0214 if (split <= 0) { 0215 return emptyMap; // not a valid upgrade hook 0216 } 0217 QString key = line.left(split); 0218 QString value = trimLeft(line, split + 1); 0219 fields[key] = value; 0220 lastKey = key; 0221 } 0222 } while (!line.isNull()); 0223 0224 return fields; 0225 } 0226 0227 bool Hook::isNotificationRequired() const 0228 { 0229 if (m_finished) { 0230 return false; 0231 } 0232 0233 if (getField("DontShowAfterReboot") == "True") { 0234 float uptime = getUptime(); 0235 if (uptime > 0) { 0236 const QDateTime now = QDateTime::currentDateTime(); 0237 QDateTime statTime = QFileInfo(m_hookPath).lastModified(); 0238 if (now.toTime_t() - statTime.toTime_t() > uptime) { 0239 return false; 0240 } 0241 } 0242 } 0243 0244 QString condition = getField("DisplayIf"); 0245 if (!condition.isEmpty()) { 0246 KProcess displayIfProcess; 0247 // Do not ever try to use a program call here. The spec defines that 0248 // DisplayIf is a shell command, if one tries to evaluate a somewhat 0249 // complex shell command as a program KProcess will die a horrible death. 0250 displayIfProcess.setShellCommand(condition); 0251 int programResult = displayIfProcess.execute(); 0252 if (programResult != 0) { 0253 return false; 0254 } 0255 } 0256 0257 return true; 0258 }