commit
29c1eb8abb
@ -0,0 +1,65 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(lubuntu-update VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
|
||||||
|
set(TS_FILES translations/lubuntu-update_en_US.ts)
|
||||||
|
|
||||||
|
set(PROJECT_SOURCES
|
||||||
|
main.cpp
|
||||||
|
mainwindow.cpp
|
||||||
|
mainwindow.h
|
||||||
|
mainwindow.ui
|
||||||
|
orchestrator.h
|
||||||
|
orchestrator.cpp
|
||||||
|
aptmanager.h
|
||||||
|
aptmanager.cpp
|
||||||
|
conffilewidget.h
|
||||||
|
conffilewidget.cpp
|
||||||
|
conffilewidget.ui
|
||||||
|
diffdisplaydialog.h
|
||||||
|
diffdisplaydialog.cpp
|
||||||
|
diffdisplaydialog.ui
|
||||||
|
conffilehandlerdialog.h
|
||||||
|
conffilehandlerdialog.cpp
|
||||||
|
conffilehandlerdialog.ui
|
||||||
|
${TS_FILES}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
|
qt_add_executable(lubuntu-update
|
||||||
|
MANUAL_FINALIZATION
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
resources.qrc
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
|
else()
|
||||||
|
add_executable(lubuntu-update
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
resources.qrc
|
||||||
|
)
|
||||||
|
|
||||||
|
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(lubuntu-update PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
|
||||||
|
install(TARGETS lubuntu-update
|
||||||
|
BUNDLE DESTINATION .
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
|
qt_finalize_executable(lubuntu-update)
|
||||||
|
endif()
|
@ -0,0 +1,21 @@
|
|||||||
|
# Lubuntu Update
|
||||||
|
|
||||||
|
Copyright (c) 2023-2024 Lubuntu Contributors. Licensed under the GNU General Public License version 2, or (at your option) any later version.
|
||||||
|
|
||||||
|
Build dependencies are Qt 5.15 and cmake, runtime dependencies are apt, apt-get, and diff.
|
||||||
|
|
||||||
|
To build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd lubuntu-update
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
To use, copy the lubuntu-update-backend script to /usr/bin/lubuntu-update-backend, then compile and run the updater. It is highly recommended that you **do not** install the updater all the way into your system with `sudo make install` or similar.
|
||||||
|
|
||||||
|
It is highly recommended that you use a Lubuntu virtual machine for testing and development. Use the latest development release if at all possible, unless you know you need to test an earlier release.
|
||||||
|
|
||||||
|
Qt Creator is recommended for editing the code. It is present in Ubuntu's official repos and can be installed using `sudo apt install qtcreator`.
|
@ -0,0 +1,315 @@
|
|||||||
|
#include "aptmanager.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
AptManager::AptManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::applyFullUpgrade()
|
||||||
|
{
|
||||||
|
internalUpdateProgress = 0;
|
||||||
|
internalUpdateInfo = getUpdateInfo();
|
||||||
|
|
||||||
|
// these six vars are used to track how far along things are for updating the progress bar
|
||||||
|
instUnpackList = instConfigList = internalUpdateInfo[0];
|
||||||
|
upgradeUnpackList = upgradeConfigList = internalUpdateInfo[1];
|
||||||
|
removeList = internalUpdateInfo[2];
|
||||||
|
numPackagesToPrep = instUnpackList.count() + upgradeUnpackList.count();
|
||||||
|
|
||||||
|
aptProcess = new QProcess();
|
||||||
|
aptProcess->setProgram("/usr/bin/lxqt-sudo");
|
||||||
|
// Note that the lubuntu-update-backend script sets LC_ALL=C in it already, so we don't need to add that here.
|
||||||
|
aptProcess->setArguments(QStringList() << "/usr/bin/lubuntu-update-backend" << "doupdate");
|
||||||
|
aptProcess->setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
QObject::connect(aptProcess, &QProcess::readyRead, this, &AptManager::handleProcessBuffer);
|
||||||
|
QObject::connect(aptProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &AptManager::handleProcessBuffer);
|
||||||
|
aptProcess->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::keepConffile(QString conffile)
|
||||||
|
{
|
||||||
|
aptProcess->write("keep\n");
|
||||||
|
aptProcess->write(conffile.toUtf8() + "\n");
|
||||||
|
aptProcess->waitForBytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::replaceConffile(QString conffile)
|
||||||
|
{
|
||||||
|
aptProcess->write("replace\n");
|
||||||
|
aptProcess->write(conffile.toUtf8() + "\n");
|
||||||
|
aptProcess->waitForBytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::doneWithConffiles()
|
||||||
|
{
|
||||||
|
aptProcess->write("done\n");
|
||||||
|
aptProcess->waitForBytesWritten();
|
||||||
|
aptProcess->closeWriteChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::handleProcessBuffer()
|
||||||
|
{
|
||||||
|
int maxWaitTime = 20;
|
||||||
|
while (!aptProcess->canReadLine() && maxWaitTime > 0) {
|
||||||
|
// this is horrible why doesn't QProcess have canReadLine signal
|
||||||
|
QThread::msleep(20);
|
||||||
|
maxWaitTime--;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression dlRegex("^Get:\\d+");
|
||||||
|
char lineBuf[2048];
|
||||||
|
|
||||||
|
while(aptProcess->canReadLine()) {
|
||||||
|
aptProcess->readLine(lineBuf, 2048);
|
||||||
|
QString line = QString(lineBuf);
|
||||||
|
emit logLineReady(line); // this tells the main window to print the line to the log view
|
||||||
|
QRegularExpressionMatch dlLineMatch = dlRegex.match(line);
|
||||||
|
|
||||||
|
// yes, this gave me a headache also
|
||||||
|
if (dlLineMatch.hasMatch()) { // Increments the progress counter for each package downloaded
|
||||||
|
internalUpdateProgress++;
|
||||||
|
} else if (line.count() >= 25 && line.left(24) == "Preparing to unpack .../" && numPackagesToPrep != 0) {
|
||||||
|
internalUpdateProgress++; // Increments the progress counter for each package that is "prepared to unpack"
|
||||||
|
numPackagesToPrep--;
|
||||||
|
} else if (line.count() >= 10 && line.left(9) == "Unpacking") {
|
||||||
|
/*
|
||||||
|
* Increments the progress counter for each package that is unpacked
|
||||||
|
* The package name may be suffixed with ":amd64" or some other
|
||||||
|
* :architecture string, so we have to split that off
|
||||||
|
* This and the subsequent progress updaters are complex since
|
||||||
|
* we don't want any line that starts with "Unpacking" or some
|
||||||
|
* other simple string to cause a progress increment. What if a
|
||||||
|
* maintainer script says it's unpacking something?
|
||||||
|
*/
|
||||||
|
QStringList parts = line.split(' ');
|
||||||
|
QString packageName;
|
||||||
|
if (parts.count() >= 2) {
|
||||||
|
packageName = parts[1].split(':')[0]; // strip off a trailing :amd64 if it's there
|
||||||
|
if (instUnpackList.removeAll(packageName) || upgradeUnpackList.removeAll(packageName)) {
|
||||||
|
internalUpdateProgress++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line.count() >= 11 && line.left(10) == "Setting up") {
|
||||||
|
QStringList parts = line.split(' ');
|
||||||
|
QString packageName;
|
||||||
|
if (parts.count() >= 3) {
|
||||||
|
packageName = parts[2].split(':')[0];
|
||||||
|
if (instConfigList.removeAll(packageName) || upgradeConfigList.removeAll(packageName)) {
|
||||||
|
internalUpdateProgress++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line.count() >= 9 && line.left(8) == "Removing") {
|
||||||
|
QStringList parts = line.split(' ');
|
||||||
|
QString packageName;
|
||||||
|
if (parts.count() >= 2) {
|
||||||
|
packageName = parts[1].split(':')[0];
|
||||||
|
if (removeList.removeAll(packageName)) {
|
||||||
|
internalUpdateProgress++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line == "Lubuntu Update !!! CONFIGURATION FILE LIST START\r\n") {
|
||||||
|
// oh boy, conffile processing. Stop everything and get the full list of conffiles, busy-waiting as needed.
|
||||||
|
QStringList conffileList;
|
||||||
|
while (true) {
|
||||||
|
while (!aptProcess->canReadLine()) {
|
||||||
|
QThread::msleep(20);
|
||||||
|
}
|
||||||
|
aptProcess->readLine(lineBuf, 2048);
|
||||||
|
QString confLine = QString(lineBuf);
|
||||||
|
confLine = confLine.left(confLine.count() - 2);
|
||||||
|
if (confLine == "Lubuntu Update !!! CONFIGURATION FILE LIST END") {
|
||||||
|
emit conffileListReady(conffileList); // this triggers the main window to show the conffile handler window
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
conffileList.append(confLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double percentageDone = (static_cast<double>(internalUpdateProgress) / (((internalUpdateInfo[0].count() + internalUpdateInfo[1].count()) * 4) + internalUpdateInfo[2].count())) * 100;
|
||||||
|
if (percentageDone > 100.0) {
|
||||||
|
percentageDone = 100.0;
|
||||||
|
}
|
||||||
|
emit progressUpdated(static_cast<int>(percentageDone));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aptProcess->state() == QProcess::NotRunning) {
|
||||||
|
emit progressUpdated(100); // just in case the progress bar didn't fill all the way due to a previous partial download
|
||||||
|
emit updateComplete();
|
||||||
|
aptProcess->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QStringList> AptManager::getUpdateInfo()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We use `apt-get -s full-upgrade` here rather than `apt list
|
||||||
|
* --upgradable` because it tells us not only what packages need upgraded,
|
||||||
|
* but also which packages need installed and removed, as well as which
|
||||||
|
* packages have been held back. The only thing this doesn't tell us is
|
||||||
|
* which packages are security updates, and for that we do use `apt list
|
||||||
|
* --upgradable`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QList<QStringList> output;
|
||||||
|
|
||||||
|
QProcess *checker = new QProcess();
|
||||||
|
QProcessEnvironment checkerEnv;
|
||||||
|
checkerEnv.insert("LC_ALL", "C");
|
||||||
|
checker->setProcessEnvironment(checkerEnv);
|
||||||
|
checker->setProgram("/usr/bin/apt-get");
|
||||||
|
checker->setArguments(QStringList() << "-s" << "full-upgrade");
|
||||||
|
checker->start();
|
||||||
|
|
||||||
|
if (checker->waitForFinished(60000)) {
|
||||||
|
QString stdoutString = QString(checker->readAllStandardOutput());
|
||||||
|
QTextStream stdoutStream(&stdoutString);
|
||||||
|
QString stdoutLine;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* output[0] = packages to install
|
||||||
|
* output[1] = packages to upgrade
|
||||||
|
* output[2] = packages to remove
|
||||||
|
* output[3] = held packages
|
||||||
|
* output[5] = security-related updates, this is populated at the end
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (int i = 0; i < 4;i++) {
|
||||||
|
output.append(QStringList());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gettingInstallPackages = false;
|
||||||
|
bool gettingUpgradePackages = false;
|
||||||
|
bool gettingUninstallPackages = false;
|
||||||
|
bool gettingHeldPackages = false;
|
||||||
|
bool gettingPackageList = false;
|
||||||
|
|
||||||
|
while (stdoutStream.readLineInto(&stdoutLine)) {
|
||||||
|
if (!gettingPackageList) {
|
||||||
|
parseAptLine(stdoutLine, &gettingInstallPackages, &gettingUpgradePackages, &gettingUninstallPackages, &gettingHeldPackages, &gettingPackageList);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Each line of output apt-get gives when displaying package
|
||||||
|
* lists is intended by two spaces. A package name will always
|
||||||
|
* consist of at least one character, so if there are less
|
||||||
|
* than three characters or the line isn't indented with two
|
||||||
|
* spaces, we know we're no longer reading a package list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (stdoutLine.count() < 3 || stdoutLine.left(2) != " ") {
|
||||||
|
gettingInstallPackages = false;
|
||||||
|
gettingUpgradePackages = false;
|
||||||
|
gettingUninstallPackages = false;
|
||||||
|
gettingHeldPackages = false;
|
||||||
|
gettingPackageList = false;
|
||||||
|
|
||||||
|
parseAptLine(stdoutLine, &gettingInstallPackages, &gettingUpgradePackages, &gettingUninstallPackages, &gettingHeldPackages, &gettingPackageList);
|
||||||
|
} else {
|
||||||
|
QString packageLine = stdoutLine.trimmed();
|
||||||
|
QStringList packageNames = packageLine.split(' ');
|
||||||
|
|
||||||
|
if (gettingInstallPackages) {
|
||||||
|
for (int i = 0;i < packageNames.count();i++) {
|
||||||
|
output[0].append(packageNames[i]);
|
||||||
|
}
|
||||||
|
} else if (gettingUpgradePackages) {
|
||||||
|
for (int i = 0;i < packageNames.count();i++) {
|
||||||
|
output[1].append(packageNames[i]);
|
||||||
|
}
|
||||||
|
} else if (gettingUninstallPackages) {
|
||||||
|
for (int i = 0;i < packageNames.count();i++) {
|
||||||
|
output[2].append(packageNames[i]);
|
||||||
|
}
|
||||||
|
} else if (gettingHeldPackages) {
|
||||||
|
for (int i = 0;i < packageNames.count();i++) {
|
||||||
|
output[3].append(packageNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(getSecurityUpdateList());
|
||||||
|
}
|
||||||
|
checker->terminate();
|
||||||
|
checker->deleteLater();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AptManager::parseAptLine(QString line, bool *gettingInstallPackages, bool *gettingUpgradePackages, bool *gettingUninstallPackages, bool *gettingHeldPackages, bool *gettingPackageList)
|
||||||
|
{
|
||||||
|
*gettingPackageList = true;
|
||||||
|
|
||||||
|
if (line == "The following NEW packages will be installed:") {
|
||||||
|
*gettingInstallPackages = true;
|
||||||
|
} else if (line == "The following packages will be upgraded:") {
|
||||||
|
*gettingUpgradePackages = true;
|
||||||
|
} else if (line == "The following packages will be REMOVED:") {
|
||||||
|
*gettingUninstallPackages = true;
|
||||||
|
} else if (line == "The following packages have been kept back:") {
|
||||||
|
*gettingHeldPackages = true;
|
||||||
|
} else {
|
||||||
|
*gettingPackageList = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList AptManager::getSecurityUpdateList()
|
||||||
|
{
|
||||||
|
QStringList updateList;
|
||||||
|
QString distroName;
|
||||||
|
|
||||||
|
// Find the distro name
|
||||||
|
QFile lsbReleaseFile("/etc/lsb-release");
|
||||||
|
lsbReleaseFile.open(QIODevice::ReadOnly);
|
||||||
|
QString contents = QString(lsbReleaseFile.readAll());
|
||||||
|
lsbReleaseFile.close();
|
||||||
|
QTextStream distroFinder(&contents);
|
||||||
|
QString distroLine;
|
||||||
|
while (distroFinder.readLineInto(&distroLine)) {
|
||||||
|
// The line has to be at least 18 characters long - 16 for the string "DISTRIB_CODENAME", one for the = sign, and one for a codename with a length of at least one.
|
||||||
|
if (distroLine.count() >= 18 && distroLine.left(16) == "DISTRIB_CODENAME") {
|
||||||
|
QStringList distroParts = distroLine.split('=');
|
||||||
|
if (distroParts.count() >= 2) {
|
||||||
|
distroName = distroParts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check for security updates
|
||||||
|
QProcess *checker = new QProcess();
|
||||||
|
QProcessEnvironment checkerEnv;
|
||||||
|
checkerEnv.insert("LC_ALL", "C");
|
||||||
|
checker->setProcessEnvironment(checkerEnv);
|
||||||
|
checker->setProgram("/usr/bin/apt");
|
||||||
|
checker->setArguments(QStringList() << "list" << "--upgradable");
|
||||||
|
checker->start();
|
||||||
|
|
||||||
|
if (checker->waitForFinished(60000)) {
|
||||||
|
QRegularExpression regex(QString("%1-security").arg(distroName));
|
||||||
|
QString stdoutString = checker->readAllStandardOutput();
|
||||||
|
QTextStream stdoutStream(&stdoutString);
|
||||||
|
QString stdoutLine;
|
||||||
|
|
||||||
|
while (stdoutStream.readLineInto(&stdoutLine)) {
|
||||||
|
QRegularExpressionMatch match = regex.match(stdoutLine);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
// The package name we want is followed immediately by a single forward slash, so we can use that as our delimiter.
|
||||||
|
QStringList updateParts = stdoutLine.split('/');
|
||||||
|
updateList.append(updateParts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker->terminate();
|
||||||
|
checker->deleteLater();
|
||||||
|
return updateList;
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
#ifndef APTMANAGER_H
|
||||||
|
#define APTMANAGER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QProcess;
|
||||||
|
class QProgressBar;
|
||||||
|
class QPlainTextEdit;
|
||||||
|
class QSystemTrayIcon;
|
||||||
|
|
||||||
|
class AptManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
AptManager(QObject *parent = nullptr);
|
||||||
|
static QList<QStringList> getUpdateInfo();
|
||||||
|
void applyFullUpgrade();
|
||||||
|
void keepConffile(QString conffile);
|
||||||
|
void replaceConffile(QString conffile);
|
||||||
|
void doneWithConffiles();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateComplete();
|
||||||
|
void progressUpdated(int progress);
|
||||||
|
void logLineReady(QString logLine);
|
||||||
|
void conffileListReady(QStringList conffileList);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleProcessBuffer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void parseAptLine(QString line, bool *gettingInstallPackages, bool *gettingUpgradePackages, bool *gettingUninstallPackages, bool *gettingHeldPackages, bool *gettingPackageList);
|
||||||
|
static QStringList getSecurityUpdateList();
|
||||||
|
QProcess *aptProcess;
|
||||||
|
QList<QStringList> internalUpdateInfo;
|
||||||
|
QStringList instUnpackList;
|
||||||
|
QStringList instConfigList;
|
||||||
|
QStringList upgradeUnpackList;
|
||||||
|
QStringList upgradeConfigList;
|
||||||
|
QStringList removeList;
|
||||||
|
quint32 numPackagesToPrep;
|
||||||
|
quint32 internalUpdateProgress;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APTMANAGER_H
|
@ -0,0 +1,37 @@
|
|||||||
|
#include "conffilehandlerdialog.h"
|
||||||
|
#include "conffilewidget.h"
|
||||||
|
#include "ui_conffilehandlerdialog.h"
|
||||||
|
|
||||||
|
ConffileHandlerDialog::ConffileHandlerDialog(const QStringList &conffiles, QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::ConffileHandlerDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
for (QString conffile : conffiles) {
|
||||||
|
ConffileWidget *conffileWidget = new ConffileWidget(conffile);
|
||||||
|
conffileList.append(conffileWidget);
|
||||||
|
ui->conffileStack->insertWidget(ui->conffileStack->count() - 1, conffileWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->doneButton, &QPushButton::clicked, this, &ConffileHandlerDialog::onDoneClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConffileHandlerDialog::~ConffileHandlerDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ConffileHandlerDialog::getReplaceConffileList()
|
||||||
|
{
|
||||||
|
return replaceConffileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConffileHandlerDialog::onDoneClicked()
|
||||||
|
{
|
||||||
|
for (ConffileWidget *w : conffileList) {
|
||||||
|
if (w->doReplace()) {
|
||||||
|
replaceConffileList.append(w->filename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->done(0);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef CONFFILEHANDLERDIALOG_H
|
||||||
|
#define CONFFILEHANDLERDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class ConffileWidget;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ConffileHandlerDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConffileHandlerDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConffileHandlerDialog(const QStringList &conffiles, QWidget *parent = nullptr);
|
||||||
|
~ConffileHandlerDialog();
|
||||||
|
QStringList getReplaceConffileList();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDoneClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ConffileHandlerDialog *ui;
|
||||||
|
QList<ConffileWidget *> conffileList;
|
||||||
|
QStringList replaceConffileList;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONFFILEHANDLERDIALOG_H
|
@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConffileHandlerDialog</class>
|
||||||
|
<widget class="QDialog" name="ConffileHandlerDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>937</width>
|
||||||
|
<height>399</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Configuration File Conflicts</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/res/images/update.svg</normaloff>:/res/images/update.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p>Some of the newly installed updates have updated configuration files. </p><p>Please choose what to do with these files.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>917</width>
|
||||||
|
<height>294</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="conffileStack">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>190</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="doneButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Done</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,45 @@
|
|||||||
|
#include "conffilewidget.h"
|
||||||
|
#include "diffdisplaydialog.h"
|
||||||
|
#include "ui_conffilewidget.h"
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
ConffileWidget::ConffileWidget(QString filename, QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::ConffileWidget)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
internalFilename = filename;
|
||||||
|
ui->filenameLabel->setText(filename);
|
||||||
|
ui->keepRadioButton->setChecked(true);
|
||||||
|
connect(ui->showDiffButton, &QPushButton::clicked, this, &ConffileWidget::onDiffClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConffileWidget::~ConffileWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConffileWidget::filename()
|
||||||
|
{
|
||||||
|
return internalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConffileWidget::doReplace()
|
||||||
|
{
|
||||||
|
return ui->replaceRadioButton->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConffileWidget::onDiffClicked()
|
||||||
|
{
|
||||||
|
QProcess diffProcess;
|
||||||
|
diffProcess.setProgram("/usr/bin/diff");
|
||||||
|
diffProcess.setArguments(QStringList() << "-u" << internalFilename << internalFilename + ".dpkg-dist");
|
||||||
|
diffProcess.start();
|
||||||
|
diffProcess.waitForFinished();
|
||||||
|
QString result = diffProcess.readAllStandardOutput();
|
||||||
|
DiffDisplayDialog ddw(internalFilename, result);
|
||||||
|
ddw.exec();
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef CONFFILEWIDGET_H
|
||||||
|
#define CONFFILEWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ConffileWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConffileWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConffileWidget(QString filename, QWidget *parent = nullptr);
|
||||||
|
~ConffileWidget();
|
||||||
|
QString filename();
|
||||||
|
bool doReplace();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDiffClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ConffileWidget *ui;
|
||||||
|
QString internalFilename;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONFFILEWIDGET_H
|
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConffileWidget</class>
|
||||||
|
<widget class="QWidget" name="ConffileWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>557</width>
|
||||||
|
<height>43</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="filenameLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Filename</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>181</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="keepRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Keep old</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="replaceRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Replace with new</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="showDiffButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show diff</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,22 @@
|
|||||||
|
#include "diffdisplaydialog.h"
|
||||||
|
#include "ui_diffdisplaydialog.h"
|
||||||
|
|
||||||
|
DiffDisplayDialog::DiffDisplayDialog(QString filename, QString diff, QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::DiffDisplayDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
this->setWindowTitle(filename + " diff");
|
||||||
|
ui->diffView->setPlainText(diff);
|
||||||
|
connect(ui->doneButton, &QPushButton::clicked, this, &DiffDisplayDialog::onDoneClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiffDisplayDialog::~DiffDisplayDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffDisplayDialog::onDoneClicked()
|
||||||
|
{
|
||||||
|
this->done(0);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef DIFFDISPLAYDIALOG_H
|
||||||
|
#define DIFFDISPLAYDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class DiffDisplayDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiffDisplayDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DiffDisplayDialog(QString filename, QString diff, QWidget *parent = nullptr);
|
||||||
|
~DiffDisplayDialog();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDoneClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::DiffDisplayDialog *ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DIFFDISPLAYDIALOG_H
|
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>DiffDisplayDialog</class>
|
||||||
|
<widget class="QDialog" name="DiffDisplayDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>704</width>
|
||||||
|
<height>553</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/res/images/update.svg</normaloff>:/res/images/update.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p>Lines that start with a &quot;+&quot; only exist in the <span style=" font-weight:700;">new</span> file.</p><p>Lines that start with a &quot;-&quot; only exist in the <span style=" font-weight:700;">old</span> file.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="diffView">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">font: 9pt "Monospace";</string>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="doneButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Done</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Backend script for Lubuntu Update (does text processing and update installation, can be run as root and must be in order to install updates)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
export LC_ALL='C'
|
||||||
|
|
||||||
|
if [ "$1" = 'pkgver' ]; then
|
||||||
|
shift
|
||||||
|
while [ "$1" != '' ]; do
|
||||||
|
source="$(apt-cache show "$1" | grep 'Source:' | cut -d' ' -f2)"
|
||||||
|
version="$(apt-cache show lubuntu-default-settings | grep Version: | head -n1 | cut -d' ' -f2)"
|
||||||
|
if [ "$source" = '' ]; then
|
||||||
|
echo "$1"
|
||||||
|
else
|
||||||
|
echo "$source"
|
||||||
|
fi
|
||||||
|
echo "$version"
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
elif [ "$1" = 'doupdate' ]; then
|
||||||
|
# Prepare to be able to grep through the logs
|
||||||
|
rm /run/lubuntu-update-apt-log || true
|
||||||
|
touch /run/lubuntu-update-apt-log
|
||||||
|
chmod 0600 /run/lubuntu-update-apt-log # prevent non-root from being able to trick the script into deleting arbitrary files later on
|
||||||
|
|
||||||
|
# Repair an interrupted upgrade if necessary
|
||||||
|
dpkg --configure -a
|
||||||
|
|
||||||
|
# Run the real update
|
||||||
|
DEBIAN_FRONTEND='kde' apt-get -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' -o Apt::Color='0' -o Dpkg::Use-Pty='0' -y dist-upgrade |& tee /run/lubuntu-update-apt-log
|
||||||
|
|
||||||
|
# Find all the conffiles
|
||||||
|
mapfile conffileRawList <<< "$(grep -P "^Configuration file \'.*\'$" '/run/lubuntu-update-apt-log')"
|
||||||
|
if [ "$(echo "${conffileRawList[0]}" | head -c1)" != 'C' ]; then # Empty or invalid list, we're done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
conffileList=()
|
||||||
|
counter=0
|
||||||
|
while [ "$counter" -lt "${#conffileRawList[@]}" ]; do
|
||||||
|
# Cut off "Configuration file '" from the start and "'" plus a couple trailing characters from the end
|
||||||
|
conffileList[counter]="$(echo "${conffileRawList[$counter]}" | tail -c+21 | head -c-3)"
|
||||||
|
counter=$((counter+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Lubuntu Update !!! CONFIGURATION FILE LIST START";
|
||||||
|
counter=0
|
||||||
|
while [ "$counter" -lt "${#conffileList[@]}" ]; do
|
||||||
|
echo "${conffileList[$counter]}"
|
||||||
|
counter=$((counter+1))
|
||||||
|
done
|
||||||
|
echo "Lubuntu Update !!! CONFIGURATION FILE LIST END";
|
||||||
|
|
||||||
|
# If we make it this far, there were conffiles to deal with
|
||||||
|
breakLoop='no'
|
||||||
|
gotCommand='no'
|
||||||
|
commandName=''
|
||||||
|
while [ "$breakLoop" = 'no' ]; do
|
||||||
|
read -r inputVal
|
||||||
|
if [ "$gotCommand" = 'no' ]; then
|
||||||
|
if [ "$inputVal" = 'done' ]; then
|
||||||
|
breakLoop='yes'
|
||||||
|
else
|
||||||
|
commandName="$inputVal"
|
||||||
|
gotCommand='yes'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$commandName" = 'replace' ]; then # Replace an existing file
|
||||||
|
counter=0
|
||||||
|
while [ "$counter" -lt "${#conffileList[@]}" ]; do
|
||||||
|
if [ "$inputVal" = "${conffileList[$counter]}" ]; then
|
||||||
|
mv "$inputVal.dpkg-dist" "$inputVal"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
counter=$((counter+1))
|
||||||
|
done
|
||||||
|
elif [ "$commandName" = 'keep' ]; then # Keep an existing file
|
||||||
|
counter=0
|
||||||
|
while [ "$counter" -lt "${#conffileList[@]}" ]; do
|
||||||
|
if [ "$inputVal" = "${conffileList[$counter]}" ]; then
|
||||||
|
rm "$inputVal.dpkg-dist"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
counter=$((counter+1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
gotCommand='no'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo 'Update installation complete.'
|
||||||
|
fi
|
@ -0,0 +1,50 @@
|
|||||||
|
#include "orchestrator.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "conffilehandlerdialog.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
QTranslator translator;
|
||||||
|
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||||
|
for (const QString &locale : uiLanguages) {
|
||||||
|
const QString baseName = "lubuntu-update_" + QLocale(locale).name();
|
||||||
|
if (translator.load(":/i18n/" + baseName)) {
|
||||||
|
a.installTranslator(&translator);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't want the updater to stop just because the user closed it :P
|
||||||
|
a.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As this is a background process, we don't pop up any window upon
|
||||||
|
* startup. An Orchestrator object periodically checks to see if new
|
||||||
|
* updates have been detected, and offers them to the user (by displaying
|
||||||
|
* a tray icon) if so. The user can click on this tray icon to see the
|
||||||
|
* updater window.
|
||||||
|
*
|
||||||
|
* Orchestrator's constructor automatically starts the update checker, so
|
||||||
|
* there's no need to do anything with this except create it and then
|
||||||
|
* start the event loop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Orchestrator *o = new Orchestrator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an artifact from testing the conffile handler window. You can
|
||||||
|
* uncomment this and rebuild lubuntu-update in order to test the conffile
|
||||||
|
* handler UI and develop it further.
|
||||||
|
*
|
||||||
|
* ConffileHandlerDialog cfhd(QStringList() << "/home/user/testfile");
|
||||||
|
* cfhd.show();
|
||||||
|
*/
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
#include "aptmanager.h"
|
||||||
|
#include "conffilehandlerdialog.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "./ui_mainwindow.h"
|
||||||
|
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
, ui(new Ui::MainWindow)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
aptManager = new AptManager();
|
||||||
|
|
||||||
|
// The progress bar and log view are shown after the user chooses to begin installing updates
|
||||||
|
ui->progressBar->setVisible(false);
|
||||||
|
ui->logView->setVisible(false);
|
||||||
|
|
||||||
|
connect(ui->installButton, &QPushButton::clicked, this, &MainWindow::onInstallButtonClicked);
|
||||||
|
connect(ui->closeButton, &QPushButton::clicked, this, &MainWindow::onCloseButtonClicked);
|
||||||
|
connect(aptManager, &AptManager::updateComplete, this, &MainWindow::onUpdateCompleted);
|
||||||
|
connect(aptManager, &AptManager::progressUpdated, this, &MainWindow::onProgressUpdate);
|
||||||
|
connect(aptManager, &AptManager::logLineReady, this, &MainWindow::onLogLineReady);
|
||||||
|
connect(aptManager, &AptManager::conffileListReady, this, &MainWindow::onConffileListReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setUpdateInfo(QList<QStringList> updateInfo)
|
||||||
|
{
|
||||||
|
ui->packageView->clear();
|
||||||
|
// The progress bar and log view are shown after the user chooses to begin installing updates
|
||||||
|
ui->progressBar->setVisible(false);
|
||||||
|
ui->logView->setVisible(false);
|
||||||
|
ui->installButton->setEnabled(true);
|
||||||
|
ui->detailsButton->setEnabled(true);
|
||||||
|
ui->closeButton->setEnabled(true);
|
||||||
|
|
||||||
|
for (int i = 0;i < 4;i++) {
|
||||||
|
QTreeWidgetItem *installItem;
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
installItem = new QTreeWidgetItem(QStringList() << "To be installed");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
installItem = new QTreeWidgetItem(QStringList() << "To be upgraded");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
installItem = new QTreeWidgetItem(QStringList() << "To be removed");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
installItem = new QTreeWidgetItem(QStringList() << "Held back (usually temporarily)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0;j < updateInfo[i].count();j++) {
|
||||||
|
// TODO: Add security update detection here - security updates should be marked in some way
|
||||||
|
installItem->addChild(new QTreeWidgetItem(QStringList() << updateInfo[i][j]));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->packageView->addTopLevelItem(installItem);
|
||||||
|
}
|
||||||
|
ui->statLabel->setText(QString("%1 package(s) will be updated. %2 of these updates are security-related.")
|
||||||
|
.arg(QString::number(updateInfo[0].count() + updateInfo[1].count() + updateInfo[2].count()),
|
||||||
|
QString::number(updateInfo[4].count())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Don't allow the user to close the window with the close button if we've
|
||||||
|
* disabled the normal "Close" button.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!ui->closeButton->isEnabled()) {
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onInstallButtonClicked()
|
||||||
|
{
|
||||||
|
ui->progressBar->setVisible(true);
|
||||||
|
ui->logView->setVisible(true);
|
||||||
|
ui->installButton->setEnabled(false);
|
||||||
|
ui->detailsButton->setEnabled(false);
|
||||||
|
ui->closeButton->setEnabled(false);
|
||||||
|
aptManager->applyFullUpgrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onCloseButtonClicked()
|
||||||
|
{
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onUpdateCompleted()
|
||||||
|
{
|
||||||
|
ui->closeButton->setEnabled(true);
|
||||||
|
ui->progressBar->setVisible(false);
|
||||||
|
ui->statLabel->setText("Update installation complete.");
|
||||||
|
emit updatesInstalled(); // this tells the orchestrator to hide the tray icon
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onProgressUpdate(int progress)
|
||||||
|
{
|
||||||
|
ui->progressBar->setValue(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onLogLineReady(QString logLine)
|
||||||
|
{
|
||||||
|
ui->logView->textCursor().insertText(logLine);
|
||||||
|
ui->logView->moveCursor(QTextCursor::End);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onConffileListReady(QStringList conffileList)
|
||||||
|
{
|
||||||
|
ConffileHandlerDialog chd(conffileList);
|
||||||
|
chd.exec();
|
||||||
|
for (QString single : chd.getReplaceConffileList()) {
|
||||||
|
conffileList.removeAll(single);
|
||||||
|
aptManager->replaceConffile(single);
|
||||||
|
}
|
||||||
|
for (QString single : conffileList) {
|
||||||
|
aptManager->keepConffile(single);
|
||||||
|
}
|
||||||
|
aptManager->doneWithConffiles();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef MAINWINDOW_H
|
||||||
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
|
#include "aptmanager.h"
|
||||||
|
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QSystemTrayIcon;
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui { class MainWindow; }
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainWindow(QWidget *parent = nullptr);
|
||||||
|
~MainWindow();
|
||||||
|
void setUpdateInfo(QList<QStringList> updateInfo);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updatesInstalled();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onInstallButtonClicked();
|
||||||
|
void onCloseButtonClicked();
|
||||||
|
void onUpdateCompleted();
|
||||||
|
void onProgressUpdate(int progress);
|
||||||
|
void onLogLineReady(QString logLine);
|
||||||
|
void onConffileListReady(QStringList conffileList);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow *ui;
|
||||||
|
AptManager *aptManager;
|
||||||
|
};
|
||||||
|
#endif // MAINWINDOW_H
|
@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>462</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Lubuntu Update</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/res/images/update.svg</normaloff>:/res/images/update.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="statLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>0 package(s) will be updated. 0 of these updates are security-related.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="packageView">
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Packages</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="textVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="logView">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>100</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: rgb(0, 0, 0);
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
font: 9pt "Monospace";</string>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="plainText">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="installButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install Updates</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources.qrc">
|
||||||
|
<normaloff>:/res/images/update.svg</normaloff>:/res/images/update.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="detailsButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Details</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="closeButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,53 @@
|
|||||||
|
#include "orchestrator.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "aptmanager.h"
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
Orchestrator::Orchestrator(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
{
|
||||||
|
checkTimer = new QTimer(); // every time this triggers, the apt database is checked for new updates
|
||||||
|
trayIcon = new QSystemTrayIcon(); // this is shown to the user to offer updates
|
||||||
|
|
||||||
|
connect(checkTimer, &QTimer::timeout, this, &Orchestrator::checkForUpdates);
|
||||||
|
connect(trayIcon, &QSystemTrayIcon::activated, this, [this](){this->displayUpdater(updateInfo);});
|
||||||
|
connect(&updaterWindow, &MainWindow::updatesInstalled, this, &Orchestrator::handleUpdatesInstalled);
|
||||||
|
|
||||||
|
checkTimer->start(21600000); // check four times a day, at least one of those times unattended-upgrades should have refreshed the apt database
|
||||||
|
|
||||||
|
checkForUpdates(); // check immediately after launch, which usually will be immediately after boot or login
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks the apt database to see if updated software is available, and places
|
||||||
|
* the results in the updateInfo variable. If updated software is available,
|
||||||
|
* show the system tray icon and trigger a notification.
|
||||||
|
*/
|
||||||
|
void Orchestrator::checkForUpdates()
|
||||||
|
{
|
||||||
|
updateInfo = AptManager::getUpdateInfo();
|
||||||
|
if (!updateInfo[0].isEmpty() || !updateInfo[1].isEmpty() || !updateInfo[2].isEmpty() || !updateInfo[3].isEmpty()) { // no need to check updateInfo[4], it will only ever contain security updates already listed in updateInfo[1]
|
||||||
|
trayIcon->setIcon(QPixmap(":/res/images/update.svg"));
|
||||||
|
trayIcon->show();
|
||||||
|
// Yes, we do intentionally use updateInfo[1], then updateInfo[0], then updateInfo[2]. The updateInfo array is populated in a different order than the one we display in.
|
||||||
|
trayIcon->showMessage("",
|
||||||
|
QString("Updates available!\n\n%1 to upgrade, %2 to install, and %3 to remove.\n\nClick the tray icon to install the updates.")
|
||||||
|
.arg(QString::number(updateInfo[1].count()), QString::number(updateInfo[0].count()), QString::number(updateInfo[2].count())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Orchestrator::displayUpdater(QList<QStringList> updateInfo)
|
||||||
|
{
|
||||||
|
if (!updaterWindow.isVisible()) {
|
||||||
|
updaterWindow.setUpdateInfo(updateInfo);
|
||||||
|
updaterWindow.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Orchestrator::handleUpdatesInstalled()
|
||||||
|
{
|
||||||
|
trayIcon->hide();
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef ORCHESTRATOR_H
|
||||||
|
#define ORCHESTRATOR_H
|
||||||
|
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QTimer;
|
||||||
|
class QSystemTrayIcon;
|
||||||
|
|
||||||
|
class Orchestrator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit Orchestrator(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void checkForUpdates();
|
||||||
|
void displayUpdater(QList<QStringList> updateInfo);
|
||||||
|
void handleUpdatesInstalled();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer *checkTimer;
|
||||||
|
QSystemTrayIcon *trayIcon;
|
||||||
|
QList<QStringList> updateInfo;
|
||||||
|
MainWindow updaterWindow;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ORCHESTRATOR_H
|
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,5 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>res/images/update.svg</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_US"></TS>
|
Loading…
Reference in new issue