Compare commits

...

13 Commits

Author SHA1 Message Date
Aaron Rainbolt
217d71ba43
Change debconf frontend to noninteractive 2024-12-24 13:52:18 -06:00
Aaron Rainbolt
fed35d17a8 Release to Oracular. 2024-08-15 16:58:55 -05:00
Aaron Rainbolt
d418d68a29 Port to Qt6. 2024-08-13 19:33:21 -05:00
978e9bfa5f Take out of beta testing, use D-Bus rather than drop files. 2024-03-25 16:18:11 -05:00
953908831e Fix notification bug, switch to beta testing 2024-03-06 22:13:45 -06:00
9a2b5b6450 Sync with archive 2024-02-12 17:32:51 -06:00
Aaron Rainbolt
7daa5ffbb7 Minor UI tweaks 2024-02-02 12:29:44 -05:00
64ba4f8a12 Update README.md 2024-02-02 11:10:36 -06:00
9a41afd011 Major bugfixes to do-release-upgrade code 2024-02-02 11:09:29 -06:00
Aaron Rainbolt
86c512a2ff Finish do-release-upgrade support (untested) 2024-02-01 10:44:01 -05:00
Aaron Rainbolt
d71a36f0ef More do-release-upgrade progress 2024-01-30 13:43:06 -05:00
Aaron Rainbolt
1821373141 ignore a build folder in gitignore 2024-01-29 22:17:12 -05:00
Aaron Rainbolt
2a7548e4ce Add new release detection code (not very user-visible yet) 2024-01-29 21:38:08 -05:00
25 changed files with 811 additions and 153 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
CMakeLists.txt.user
build

View File

@ -9,8 +9,7 @@ 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)
find_package(Qt6 REQUIRED COMPONENTS Widgets DBus LinguistTools)
set(TS_FILES
src/translations/lubuntu-update_en_US.ts
@ -36,16 +35,22 @@ set(PROJECT_SOURCES
src/conffilehandlerdialog.h
src/conffilehandlerdialog.cpp
src/conffilehandlerdialog.ui
src/ipcfilewatcher.h
src/ipcfilewatcher.cpp
src/releaseupgradewindow.h
src/releaseupgradewindow.cpp
src/releaseupgradewindow.ui
src/upgradedelaywindow.h
src/upgradedelaywindow.cpp
src/upgradedelaywindow.ui
src/windowshowwatcher.h
src/windowshowwatcher.cpp
${TS_FILES}
)
set(TRANSLATION_RESOURCES "src/translations.qrc")
configure_file(${TRANSLATION_RESOURCES} translations.qrc COPYONLY)
qt5_add_translation(QM_FILES ${TS_FILES})
qt5_add_resources(QM_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
qt6_add_translation(QM_FILES ${TS_FILES})
qt6_add_resources(QM_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
add_custom_target(translations ALL DEPENDS ${QM_FILES})
@ -57,7 +62,9 @@ add_executable(lubuntu-update
add_dependencies(lubuntu-update translations)
target_link_libraries(lubuntu-update PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_link_libraries(lubuntu-update PRIVATE
Qt6::Widgets
Qt6::DBus)
install(TARGETS lubuntu-update
BUNDLE DESTINATION .

View File

@ -2,7 +2,7 @@
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.
Build dependencies are Qt 5.15 and cmake, runtime dependencies are apt, apt-get, curl, and diff.
To build:
@ -20,6 +20,11 @@ It is highly recommended that you use a Lubuntu virtual machine for testing and
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`.
## Config file format:
There's only one field here:
* nextDoReleaseUpgradeNotify=123456789 - Value is number of seconds since the UNIX epoch. Used to determine when to offer the user an upgrade.
## Missing features
* Double-clicking on a package doesn't show detailed information for it yet.

43
debian/changelog vendored
View File

@ -1,3 +1,46 @@
lubuntu-update-notifier (1.1.1) plucky; urgency=medium
* Switch debconf frontend to noninteractive. (LP: #2091704)
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Tue, 24 Dec 2024 13:51:28 -0600
lubuntu-update-notifier (1.1.0) oracular; urgency=medium
* Port to Qt6.
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Thu, 15 Aug 2024 16:58:41 -0500
lubuntu-update-notifier (1.0.0) noble; urgency=medium
* Change from beta to final release.
* Use D-Bus as a window show trigger rather than clunky drop files, this
will prevent issues if multiple users are logged in at once.
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Mon, 25 Mar 2024 16:15:49 -0500
lubuntu-update-notifier (1.0.0~beta1) noble; urgency=medium
* Change from alpha to beta testing phase, this has been tested for a while
and looks pretty stable so far.
* Wait to start until lxqt-notificationd is present. (LP: #2056379)
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Thu, 07 Mar 2024 04:06:13 +0000
lubuntu-update-notifier (1.0.0~alpha4) noble; urgency=medium
* Fix infinite loop when no eligible new release is available.
* Fix a bug where only the first eligible new release would be considered.
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Fri, 09 Feb 2024 22:26:59 +0000
lubuntu-update-notifier (1.0.0~alpha3) noble; urgency=medium
* New feature release. Notable additions:
- do-release-upgrade support
* Updated runtime dependencies.
-- Aaron Rainbolt <arraybolt3@ubuntu.com> Fri, 02 Feb 2024 11:37:36 -0600
lubuntu-update-notifier (1.0.0~alpha2) noble; urgency=medium
* New feature release. Notable additions:

7
debian/control vendored
View File

@ -5,14 +5,15 @@ Maintainer: Lubuntu Developers <lubuntu-devel@lists.ubuntu.com>
Build-Depends: cmake,
debhelper-compat (= 13),
lxqt-sudo (>= 1.4.0-0ubuntu2),
qtbase5-dev,
qttools5-dev
qt6-base-dev,
qt6-tools-dev
Standards-Version: 4.6.2
Rules-Requires-Root: no
Package: lubuntu-update-notifier
Architecture: any
Depends: ${misc:Depends},
Depends: curl,
${misc:Depends},
${shlibs:Depends}
Description: Lubuntu's update installer
Lubuntu Update is an enhanced, modern update installer for the Lubuntu

1
debian/files vendored Normal file
View File

@ -0,0 +1 @@
lubuntu-update-notifier_1.1.0~ppa1_source.buildinfo admin optional

View File

@ -88,10 +88,10 @@ void AptManager::handleUpdateProcessBuffer()
// 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) {
} else if (line.length() >= 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") {
} else if (line.length() >= 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
@ -109,7 +109,7 @@ void AptManager::handleUpdateProcessBuffer()
internalUpdateProgress++;
}
}
} else if (line.count() >= 11 && line.left(10) == "Setting up") {
} else if (line.length() >= 11 && line.left(10) == "Setting up") {
QStringList parts = line.split(' ');
QString packageName;
if (parts.count() >= 3) {
@ -118,7 +118,7 @@ void AptManager::handleUpdateProcessBuffer()
internalUpdateProgress++;
}
}
} else if (line.count() >= 9 && line.left(8) == "Removing") {
} else if (line.length() >= 9 && line.left(8) == "Removing") {
QStringList parts = line.split(' ');
QString packageName;
if (parts.count() >= 2) {
@ -136,7 +136,7 @@ void AptManager::handleUpdateProcessBuffer()
}
aptProcess->readLine(lineBuf, 2048);
QString confLine = QString(lineBuf);
confLine = confLine.left(confLine.count() - 2);
confLine = confLine.left(confLine.length() - 2);
if (confLine == "Lubuntu Update !!! CONFIGURATION FILE LIST END") {
emit conffileListReady(conffileList); // this triggers the main window to show the conffile handler window
break;
@ -144,6 +144,15 @@ void AptManager::handleUpdateProcessBuffer()
conffileList.append(confLine);
}
}
} else if (line == "Lubuntu Update !!! NEW RELEASE\r\n") {
// Same busy-wait technique, but here we're just getting one extra line, the release code.
while (!aptProcess->canReadLine()) {
QThread::msleep(20);
}
aptProcess->readLine(lineBuf, 2048);
QString releaseCode = QString(lineBuf);
releaseCode = releaseCode.left(releaseCode.length() - 2);
emit newRelease(releaseCode);
}
double percentageDone = (static_cast<double>(internalUpdateProgress) / (((internalUpdateInfo[0].count() + internalUpdateInfo[1].count()) * 4) + internalUpdateInfo[2].count())) * 100;
@ -237,7 +246,7 @@ QList<QStringList> AptManager::getUpdateInfo()
* spaces, we know we're no longer reading a package list.
*/
if (stdoutLine.count() < 3 || stdoutLine.left(2) != " ") {
if (stdoutLine.length() < 3 || stdoutLine.left(2) != " ") {
gettingInstallPackages = false;
gettingUpgradePackages = false;
gettingUninstallPackages = false;
@ -308,7 +317,7 @@ QStringList AptManager::getSecurityUpdateList()
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") {
if (distroLine.length() >= 18 && distroLine.left(16) == "DISTRIB_CODENAME") {
QStringList distroParts = distroLine.split('=');
if (distroParts.count() >= 2) {
distroName = distroParts[1];

View File

@ -28,6 +28,7 @@ signals:
void progressUpdated(int progress);
void logLineReady(QString logLine);
void conffileListReady(QStringList conffileList);
void newRelease(QString code);
private slots:
void handleUpdateProcessBuffer();

View File

@ -1,38 +0,0 @@
#include "ipcfilewatcher.h"
#include <QApplication>
#include <QDir>
#include <QFile>
#include <QFileSystemWatcher>
#include <QDebug>
IPCFileWatcher::IPCFileWatcher(QObject *parent)
: QObject{parent}
{
QDir targetDir("/dev/shm/lubuntu-update");
bool couldRemove = targetDir.removeRecursively();
if (!couldRemove) {
qCritical() << "Could not clear IPC directory. Ensure that /dev/shm and /dev/shm/lubuntu-update are world-readable and world-writable.";
initFailed = true;
return;
}
targetDir.mkdir("/dev/shm/lubuntu-update");
QFileSystemWatcher *watcher = new QFileSystemWatcher(QStringList() << "/dev/shm/lubuntu-update");
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &IPCFileWatcher::checkForShowWindowFile);
initFailed = false;
}
bool IPCFileWatcher::didInitFail()
{
return initFailed;
}
void IPCFileWatcher::checkForShowWindowFile()
{
QFile flagFile("/dev/shm/lubuntu-update/lubuntu-update-show-win");
if (flagFile.exists()) {
flagFile.remove();
emit showWindowFlagDetected();
}
}

View File

@ -1,22 +0,0 @@
#ifndef IPCFILEWATCHER_H
#define IPCFILEWATCHER_H
#include <QObject>
class IPCFileWatcher : public QObject
{
Q_OBJECT
public:
explicit IPCFileWatcher(QObject *parent = nullptr);
bool didInitFail();
signals:
void showWindowFlagDetected();
private:
bool initFailed;
void checkForShowWindowFile();
};
#endif // IPCFILEWATCHER_H

View File

@ -4,6 +4,43 @@
set -e
export LC_ALL='C'
# Returns 0 if the release is supported, 1 if unsupported, 2 if if didn't exist at all, and 3 if something went wrong.
isReleaseSupported () {
releaseYear="${1:-}";
releaseMonth="${2:-}";
metaReleaseStr="${3:-}";
if [ -z "$releaseYear" ]; then
echo '! ! ! releaseYear is blank';
return 3;
elif [ -z "$releaseMonth" ]; then
echo '! ! ! releaseMonth is blank';
return 3;
elif [ -z "$metaReleaseStr" ]; then
echo '! ! ! metaReleaseStr is blank';
return 3;
fi
releaseCode="$releaseYear.$releaseMonth";
scanForSupported='n';
while IFS= read -r line || [[ -n $line ]]; do
if [[ "$line" =~ $releaseCode ]]; then
scanForSupported='y';
fi
if [ "$scanForSupported" = 'y' ]; then
if [[ "$line" =~ Supported ]]; then
if [ "$(echo "$line" | cut -d':' -f2 | tail -c+2)" = '0' ]; then
return 1;
else
return 0;
fi
fi
fi
done < <(printf '%s' "$metaReleaseStr")
return 2;
}
if [ "$1" = 'pkgver' ]; then
shift
while [ "$1" != '' ]; do
@ -29,66 +66,128 @@ elif [ "$1" = 'doupdate' ]; then
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
DEBIAN_FRONTEND='noninteractive' 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
doConffiles='y';
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
doConffiles='n';
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
if [ "$doConffiles" = 'y' ]; then
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";
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'
# 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
commandName="$inputVal"
gotCommand='yes'
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
fi
echo 'Checking release status...'
releaseCode="$(cat /etc/lsb-release | grep "DISTRIB_RELEASE" | cut -d'=' -f2)";
releaseYear="$(cut -d'.' -f1 <<< "$releaseCode")";
releaseMonth="$(cut -d'.' -f2 <<< "$releaseCode")";
metaReleaseData="$(curl https://changelogs.ubuntu.com/meta-release)";
#nextReleaseMonth='';
#nextReleaseYear='';
#nextLTSReleaseMonth='';
#nextLTSReleaseYear='';
while true; do
if ((releaseMonth == 4)); then
releaseMonth='10';
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'
releaseMonth='04';
((releaseYear++));
fi
releaseSupportedResult="$(isReleaseSupported "$releaseYear" "$releaseMonth" "$metaReleaseData"; echo "$?")";
if [ "$releaseSupportedResult" = '0' ]; then
echo 'Lubuntu Update !!! NEW RELEASE';
echo "$releaseYear.$releaseMonth";
elif [ "$releaseSupportedResult" = '2' ]; then
break;
fi
done
# if ((releaseMonth == 4)); then
# nextReleaseMonth=$((releaseMonth + 6));
# nextReleaseYear="$releaseYear";
# if (((releaseYear % 2) == 0)); then
# nextLTSReleaseMonth='04';
# nextLTSReleaseYear=$((releaseYear + 2));
# fi
# else
# nextReleaseMonth="$releaseMonth";
# nextReleaseYear=$((releaseYear + 1));
# fi
#
# if [ -n "$nextLTSReleaseYear" ]; then
# if isReleaseSupported "$nextLTSReleaseYear" "$nextLTSReleaseMonth" "$metaReleaseData"; then
# echo 'Lubuntu Update !!! NEW RELEASE';
# echo "$nextLTSReleaseYear.$nextLTSReleaseMonth";
# fi
# fi
#
# if ! (((nextReleaseYear == nextLTSReleaseYear) && (nextReleaseMonth == nextLTSReleaseMonth))); then
# if isReleaseSupported "$nextReleaseYear" "$nextReleaseMonth" "$metaReleaseData"; then
# echo 'Lubuntu Update !!! NEW RELEASE';
# echo "$nextReleaseYear.$nextReleaseMonth";
# else
# echo "Unsupported release: $nextReleaseYear.$nextReleaseMonth";
# fi
# fi
echo 'Update installation complete.'
elif [ "$1" = 'doReleaseUpgrade' ]; then
do-release-upgrade -m desktop -f DistUpgradeViewKDE;
elif [ "$1" = 'declineReleaseUpgrade' ]; then
sed -i -E 's/^Prompt=(never|lts|normal)$/Prompt=never/' /etc/update-manager/release-upgrades;
fi

View File

@ -1,14 +1,38 @@
#include "ipcfilewatcher.h"
#include "windowshowwatcher.h"
#include "orchestrator.h"
#include "mainwindow.h"
#include "conffilehandlerdialog.h"
#include <QApplication>
#include <QDBusConnection>
#include <QDBusError>
#include <QDBusInterface>
#include <QDebug>
#include <QDialog>
#include <QLocale>
#include <QProcess>
#include <QThread>
#include <QTranslator>
/*
* Detects if at least `count` processes that match `procName` are running.
*/
bool detectProc(QString procName, int count)
{
QProcess procDetector;
procDetector.setProgram("/usr/bin/bash");
procDetector.setArguments(QStringList() << "-c" << "ps axo comm | grep " + procName);
procDetector.start();
procDetector.waitForFinished();
QString procDetectResult = procDetector.readAllStandardOutput();
procDetectResult = procDetectResult.trimmed();
QStringList procDetectResultList = procDetectResult.split('\n');
if (procDetectResultList.count() >= count) {
return true;
}
return false;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
@ -23,40 +47,53 @@ int main(int argc, char *argv[])
}
}
/*
* If Lubuntu Update is already running, create
* /dev/shm/lubuntu-update/lubuntu-update-show-win and exit. This will
* trigger the existing process to pop up a window.
*/
// Connect to D-Bus.
auto dbusConnection = QDBusConnection::sessionBus();
QProcess procDetector;
procDetector.setProgram("/usr/bin/bash");
procDetector.setArguments(QStringList() << "-c" << "ps axo comm | grep lubuntu-update");
procDetector.start();
procDetector.waitForFinished();
QString procDetectResult = procDetector.readAllStandardOutput();
procDetectResult = procDetectResult.trimmed();
QStringList procDetectResultList = procDetectResult.split('\n');
if (procDetectResultList.count() > 1) {
QFile flagFile("/dev/shm/lubuntu-update/lubuntu-update-show-win");
flagFile.open(QFile::WriteOnly);
flagFile.close();
/*
* If Lubuntu Update is already running, instruct the running instance to
* display its window via the D-Bus connection.
*/
if (detectProc("lubuntu-update", 2)) {
auto iface = new QDBusInterface("me.lubuntu.LubuntuUpdate.window", "/", "me.lubuntu.LubuntuUpdate.window", dbusConnection);
if (!iface->isValid()) {
qWarning().noquote() << dbusConnection.lastError().message();
return 1;
}
iface->call("showWindow");
return 0;
}
/*
* Wait to run until lxqt-notificationd is running. This avoids a bug that
* causes notifications to show up in the entirely wrong spot. If it takes
* longer than about 30 seconds to show up, we continue on without it for
* the sake of getting security updates.
*/
for (int i = 0;i < 30;i++) {
// "lxqt-notificati" is intentionally truncated here since that's how it shows up in the output of `ps axo comm`.
if (detectProc("lxqt-notificati", 1)) {
// Wait for it to initialize fully - 3 seconds should be way more than enough
QThread::sleep(3);
break;
} else {
QThread::sleep(1);
}
}
// Don't want the updater to stop just because the user closed it :P
a.setQuitOnLastWindowClosed(false);
/*
* IPCFileWatcher just watches the /dev/shm/lubuntu-update folder for the
* creation of a lubuntu-update-show-win file. If it detects it, it
* immediately deletes it and emits a signal. This is then used later to
* cause the updater window to pop up.
* WindowShowWatcher is a very simple D-Bus service that allow triggering
* the Lubuntu Update window to be shown. If anything calls "showWindow"
* on this service, Lubuntu Update's window will pop up.
*/
QObject obj;
auto *wsw = new WindowShowWatcher(&obj);
dbusConnection.registerObject("/", &obj);
IPCFileWatcher *p = new IPCFileWatcher();
if (p->didInitFail()) {
if (!dbusConnection.registerService("me.lubuntu.LubuntuUpdate.window")) {
return 1;
}
@ -71,10 +108,9 @@ int main(int argc, char *argv[])
* there's no need to do anything with this except create it and then
* start the event loop.
*/
Orchestrator *o = new Orchestrator();
QObject::connect(p, &IPCFileWatcher::showWindowFlagDetected, o, &Orchestrator::displayUpdater);
QObject::connect(wsw, &WindowShowWatcher::showWindowTriggered, o, &Orchestrator::displayUpdater);
/*
* This is an artifact from testing the conffile handler window. You can

View File

@ -25,6 +25,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(aptManager, &AptManager::progressUpdated, this, &MainWindow::onProgressUpdate);
connect(aptManager, &AptManager::logLineReady, this, &MainWindow::onLogLineReady);
connect(aptManager, &AptManager::conffileListReady, this, &MainWindow::onConffileListReady);
connect(aptManager, &AptManager::newRelease, this, &MainWindow::onNewRelease);
}
MainWindow::~MainWindow()
@ -143,6 +144,9 @@ void MainWindow::onUpdateCompleted()
ui->progressBar->setVisible(false);
ui->statLabel->setText(tr("Update installation complete."));
emit updatesInstalled(); // this tells the orchestrator to hide the tray icon
if (releaseCodes.count() > 0) {
handleNewReleases();
}
}
void MainWindow::onCheckUpdatesCompleted()
@ -175,3 +179,14 @@ void MainWindow::onConffileListReady(QStringList conffileList)
}
aptManager->doneWithConffiles();
}
void MainWindow::onNewRelease(QString code)
{
releaseCodes.append(code);
}
void MainWindow::handleNewReleases()
{
emit newReleaseAvailable(releaseCodes);
releaseCodes.clear();
}

View File

@ -26,6 +26,7 @@ public:
signals:
void updatesInstalled();
void updatesRefreshed();
void newReleaseAvailable(QStringList releaseCodes);
protected slots:
void closeEvent(QCloseEvent *event) override;
@ -39,9 +40,13 @@ private slots:
void onProgressUpdate(int progress);
void onLogLineReady(QString logLine);
void onConffileListReady(QStringList conffileList);
void onNewRelease(QString code);
private:
Ui::MainWindow *ui;
AptManager *aptManager;
QStringList releaseCodes;
void handleNewReleases();
};
#endif // MAINWINDOW_H

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<width>473</width>
<height>600</height>
</rect>
</property>
@ -37,6 +37,9 @@
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>

View File

@ -1,8 +1,13 @@
#include "orchestrator.h"
#include "mainwindow.h"
#include "aptmanager.h"
#include "releaseupgradewindow.h"
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QIcon>
#include <QProcess>
#include <QSystemTrayIcon>
#include <QTimer>
@ -12,10 +17,38 @@ Orchestrator::Orchestrator(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
/*
* Parse the Lubuntu Update config file. It contains two critical pieces
* of info - when the system last offered the user a release upgrade,
* and whether the user has disabled release upgrade notifications.
*/
QFile configFile(QDir::homePath() + "/.config/lubuntu-update.conf");
bool success = configFile.open(QFile::ReadOnly);
if (success) {
char lineBuf[2048];
while (!configFile.atEnd()) {
configFile.readLine(lineBuf, 2048);
QString line(lineBuf);
line = line.trimmed();
QStringList lineParts = line.split("=");
if (lineParts.count() == 2) {
if (lineParts[0] == "nextDoReleaseUpgradeNotify") {
nextUpgradeCheck = QDateTime::fromSecsSinceEpoch(lineParts[1].toLongLong());
} else {
qWarning() << "Unrecognized config line: " << line;
}
} else {
qWarning() << "Wrong number of fields in line: " << line;
}
}
}
configFile.close();
connect(checkTimer, &QTimer::timeout, this, &Orchestrator::checkForUpdates);
connect(trayIcon, &QSystemTrayIcon::activated, this, &Orchestrator::displayUpdater);
connect(&updaterWindow, &MainWindow::updatesInstalled, this, &Orchestrator::handleUpdatesInstalled);
connect(&updaterWindow, &MainWindow::updatesRefreshed, this, &Orchestrator::handleUpdatesRefreshed);
connect(&updaterWindow, &MainWindow::newReleaseAvailable, this, &Orchestrator::onNewReleaseAvailable);
checkTimer->start(21600000); // check four times a day, at least one of those times unattended-upgrades should have refreshed the apt database
@ -65,3 +98,91 @@ void Orchestrator::handleUpdatesRefreshed()
checkForUpdates();
displayUpdater();
}
void Orchestrator::onNewReleaseAvailable(QStringList releaseCodes)
{
// First, determine what kinds of releases the user wants to see.
QFile druTypeFile("/etc/update-manager/release-upgrades");
bool success = druTypeFile.open(QFile::ReadOnly);
QString druType;
if (success) {
char lineBuf[2048];
while (!druTypeFile.atEnd()) {
druTypeFile.readLine(lineBuf, 2048);
QString line(lineBuf);
line = line.trimmed();
if (line == "Prompt=lts") {
druType="lts";
druTypeFile.close();
break;
} else if (line == "Prompt=none") {
// The user has disabled all upgrade prompts.
druTypeFile.close();
return;
} else if (line == "Prompt=normal") {
druType="normal";
druTypeFile.close();
break;
}
}
} else {
druType="normal";
druTypeFile.close();
}
for (int i = 0;i < releaseCodes.count();i++) {
QStringList releaseCodeParts = releaseCodes[i].split('.');
if (releaseCodeParts.count() >= 2) {
int releaseYear = releaseCodeParts[0].toInt();
int releaseMonth = releaseCodeParts[1].toInt();
if (((releaseYear % 2 == 0) && (releaseMonth == 4)) || druType == "normal") {
QDateTime now = QDateTime::currentDateTime();
if (nextUpgradeCheck < now) {
ReleaseUpgradeWindow upgradeWindow(releaseCodes[i]);
upgradeWindow.exec();
if (upgradeWindow.getUpgradeAccepted()) {
doReleaseUpgrade();
} else if (upgradeWindow.getUpgradeDelayStamp() > 0) {
delayReleaseUpgrade(upgradeWindow.getUpgradeDelayStamp());
} else {
declineReleaseUpgrade();
}
break;
}
}
}
}
}
void Orchestrator::doReleaseUpgrade()
{
QProcess druProcess;
druProcess.setProgram("/usr/bin/lxqt-sudo");
druProcess.setArguments(QStringList() << "/usr/libexec/lubuntu-update-backend" << "doReleaseUpgrade");
druProcess.start();
druProcess.waitForFinished(-1);
}
void Orchestrator::delayReleaseUpgrade(qint64 timestamp)
{
QFile configFile(QDir::homePath() + "/.config/lubuntu-update.conf");
bool success = configFile.open(QFile::WriteOnly);
if (success) {
configFile.write("nextDoReleaseUpgradeNotify=");
configFile.write(QString::number(timestamp).toUtf8());
configFile.write("\n");
} else {
qWarning() << "Could not write to " + QDir::homePath() + "/.config/lubuntu-update.conf, check permissions";
}
configFile.close();
nextUpgradeCheck = QDateTime::fromSecsSinceEpoch(timestamp);
}
void Orchestrator::declineReleaseUpgrade()
{
QProcess druProcess;
druProcess.setProgram("/usr/bin/lxqt-sudo");
druProcess.setArguments(QStringList() << "/usr/libexec/lubuntu-update-backend" << "declineReleaseUpgrade");
druProcess.start();
druProcess.waitForFinished(-1);
}

View File

@ -3,11 +3,13 @@
#include "mainwindow.h"
#include <QDateTime>
#include <QObject>
#include <QStringList>
class QTimer;
class QSystemTrayIcon;
class ReleaseUpgradeWindow;
class Orchestrator : public QObject
{
@ -22,12 +24,17 @@ private slots:
void checkForUpdates();
void handleUpdatesInstalled();
void handleUpdatesRefreshed();
void onNewReleaseAvailable(QStringList releaseCodes);
private:
QTimer *checkTimer;
QSystemTrayIcon *trayIcon;
QList<QStringList> updateInfo;
MainWindow updaterWindow;
QDateTime nextUpgradeCheck;
void doReleaseUpgrade();
void delayReleaseUpgrade(qint64 timestamp);
void declineReleaseUpgrade();
};
#endif // ORCHESTRATOR_H

View File

@ -0,0 +1,67 @@
#include "releaseupgradewindow.h"
#include "ui_releaseupgradewindow.h"
#include "upgradedelaywindow.h"
#include <QDateTime>
#include <QMessageBox>
ReleaseUpgradeWindow::ReleaseUpgradeWindow(QString releaseCode, QWidget *parent) :
QDialog(parent),
ui(new Ui::ReleaseUpgradeWindow)
{
ui->setupUi(this);
ui->upgradeLabel->setText(tr("An upgrade to Lubuntu %1 is available! Would you like to install this upgrade now?").arg(releaseCode));
code=releaseCode;
connect(ui->upgradeButton, &QPushButton::clicked, this, &ReleaseUpgradeWindow::onUpgradeClicked);
connect(ui->remindButton, &QPushButton::clicked, this, &ReleaseUpgradeWindow::onRemindClicked);
connect(ui->declineButton, &QPushButton::clicked, this, &ReleaseUpgradeWindow::onDeclineClicked);
}
ReleaseUpgradeWindow::~ReleaseUpgradeWindow()
{
delete ui;
}
bool ReleaseUpgradeWindow::getUpgradeAccepted()
{
return upgradeAccepted;
}
qint64 ReleaseUpgradeWindow::getUpgradeDelayStamp()
{
return upgradeDelayStamp;
}
void ReleaseUpgradeWindow::onUpgradeClicked()
{
upgradeAccepted = true;
this->done(0);
}
void ReleaseUpgradeWindow::onRemindClicked()
{
UpgradeDelayWindow delayWindow;
delayWindow.exec();
if (delayWindow.getDelayDays() > 0) {
QDateTime delayDateTime = QDateTime::currentDateTime();
delayDateTime = delayDateTime.addDays(delayWindow.getDelayDays());
upgradeAccepted = false;
upgradeDelayStamp = delayDateTime.toSecsSinceEpoch();
this->done(0);
}
}
void ReleaseUpgradeWindow::onDeclineClicked()
{
QMessageBox confirmDialog;
confirmDialog.setWindowTitle(tr("Lubuntu Update"));
confirmDialog.setText(tr("You have declined the upgrade to Lubuntu %1.\nYou can upgrade manually by following the directions at https://manual.lubuntu.me/stable/D/upgrading.html.").arg(code));
confirmDialog.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
int result = confirmDialog.exec();
if (result == QMessageBox::Ok) {
upgradeDelayStamp = 0;
upgradeAccepted = false;
this->done(0);
}
}

View File

@ -0,0 +1,32 @@
#ifndef RELEASEUPGRADEWINDOW_H
#define RELEASEUPGRADEWINDOW_H
#include <QDialog>
namespace Ui {
class ReleaseUpgradeWindow;
}
class ReleaseUpgradeWindow : public QDialog
{
Q_OBJECT
public:
explicit ReleaseUpgradeWindow(QString releaseCode, QWidget *parent = nullptr);
~ReleaseUpgradeWindow();
bool getUpgradeAccepted();
qint64 getUpgradeDelayStamp();
private slots:
void onUpgradeClicked();
void onRemindClicked();
void onDeclineClicked();
private:
Ui::ReleaseUpgradeWindow *ui;
QString code;
bool upgradeAccepted = false;
qint64 upgradeDelayStamp = 0;
};
#endif // RELEASEUPGRADEWINDOW_H

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ReleaseUpgradeWindow</class>
<widget class="QDialog" name="ReleaseUpgradeWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<height>116</height>
</rect>
</property>
<property name="windowTitle">
<string>Lubuntu Update</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="upgradeLabel">
<property name="text">
<string>upgrade available</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="upgradeButton">
<property name="text">
<string>Upgrade Now</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remindButton">
<property name="text">
<string>Remind me later</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="declineButton">
<property name="text">
<string>Decline Upgrade</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,42 @@
#include "upgradedelaywindow.h"
#include "ui_upgradedelaywindow.h"
UpgradeDelayWindow::UpgradeDelayWindow(QWidget *parent) :
QDialog(parent),
ui(new Ui::UpgradeDelayWindow)
{
ui->setupUi(this);
connect(ui->okButton, &QPushButton::clicked, this, &UpgradeDelayWindow::onOkClicked);
connect(ui->cancelButton, &QPushButton::clicked, this, &UpgradeDelayWindow::onCancelClicked);
}
UpgradeDelayWindow::~UpgradeDelayWindow()
{
delete ui;
}
qint64 UpgradeDelayWindow::getDelayDays()
{
return delayDays;
}
void UpgradeDelayWindow::onOkClicked()
{
switch (ui->timeTypeComboBox->currentIndex()) {
case 0: // days
delayDays = ui->timeSpinBox->value();
break;
case 1: // weeks
delayDays = ui->timeSpinBox->value() * 7;
break;
case 2: // months
delayDays = ui->timeSpinBox->value() * 28;
break;
}
this->done(0);
}
void UpgradeDelayWindow::onCancelClicked()
{
this->done(0);
}

28
src/upgradedelaywindow.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef UPGRADEDELAYWINDOW_H
#define UPGRADEDELAYWINDOW_H
#include <QDialog>
namespace Ui {
class UpgradeDelayWindow;
}
class UpgradeDelayWindow : public QDialog
{
Q_OBJECT
public:
explicit UpgradeDelayWindow(QWidget *parent = nullptr);
~UpgradeDelayWindow();
qint64 getDelayDays();
private slots:
void onOkClicked();
void onCancelClicked();
private:
Ui::UpgradeDelayWindow *ui;
qint64 delayDays;
};
#endif // UPGRADEDELAYWINDOW_H

97
src/upgradedelaywindow.ui Normal file
View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpgradeDelayWindow</class>
<widget class="QDialog" name="UpgradeDelayWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>242</width>
<height>137</height>
</rect>
</property>
<property name="windowTitle">
<string>Lubuntu Update</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How long would you like to wait before being reminded of the upgrade again?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QSpinBox" name="timeSpinBox"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="timeTypeComboBox">
<item>
<property name="text">
<string>Days</string>
</property>
</item>
<item>
<property name="text">
<string>Weeks</string>
</property>
</item>
<item>
<property name="text">
<string>Months</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<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>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,6 @@
#include "windowshowwatcher.h"
void WindowShowWatcher::showWindow()
{
emit showWindowTriggered();
}

23
src/windowshowwatcher.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef WINDOWSHOWWATCHER_H
#define WINDOWSHOWWATCHER_H
#include <QObject>
#include <QDBusAbstractAdaptor>
#include <QDBusVariant>
class WindowShowWatcher : public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "me.lubuntu.LubuntuUpdate.window")
public:
explicit WindowShowWatcher(QObject *obj) : QDBusAbstractAdaptor(obj) {}
public slots:
void showWindow();
signals:
void showWindowTriggered();
};
#endif // WINDOWSHOWWATCHER_H