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