Files
snigdhaos-blackbox/qt/snigdhaosblackbox.cpp
2024-12-20 19:38:19 +05:30

396 lines
17 KiB
C++

#include "snigdhaosblackbox.h" // Includes the header file for the SnigdhaOSBlackbox class to use its declarations and functionality.
#include "./ui_snigdhaosblackbox.h" // Includes the auto-generated header file for the UI created using Qt Designer.
#include <QCheckBox> // Used to manage checkbox UI components.
#include <QDebug> // Provides tools for debugging, logging information, and printing messages to the console.
#include <QFileInfo> // Allows access to file metadata, such as checking file modification times.
#include <QProcess> // Used to manage and interact with external processes (such as running commands in the terminal).
#include <QScrollArea> // Provides a scrollable area in the UI to allow navigation through large widgets.
#include <QTemporaryFile> // Creates temporary files that are automatically deleted after use.
#include <QTimer> // Provides functionality for scheduling tasks with delays or intervals.
#include <QtNetwork/QNetworkReply> // Handles responses from network requests (used to check internet connectivity).
#include <unistd.h> // Provides POSIX functions, used here for process management (e.g., restarting the application).
const char* INTERNET_CHECK_URL = "https://snigdha-os.github.io/"; // URL used to verify internet connectivity by sending a network request.
SnigdhaOSBlackbox::SnigdhaOSBlackbox(QWidget *parent, QString state)
: QMainWindow(parent) // Calls the constructor of the QMainWindow base class to initialize the main window with the parent widget.
, ui(new Ui::SnigdhaOSBlackbox) // Initializes the user interface (UI) for the SnigdhaOSBlackbox window, using the UI class auto-generated by Qt Designer.
{
// Sets the window icon to the specified file path, ensuring that the application window will display the given icon (SVG format).
this->setWindowIcon(QIcon("/usr/share/pixmaps/snigdhaos-blackbox.svg"));
// Initializes the user interface, setting up the UI components (buttons, labels, etc.) in the SnigdhaOSBlackbox window.
ui->setupUi(this);
// Modifies the window flags to disable the close button on the window (i.e., the application cannot be closed directly via the window).
this->setWindowFlags(this->windowFlags() & -Qt::WindowCloseButtonHint);
// Gets the last modified timestamp of the executable file (the current running application), which is useful for checking when the application was last updated.
executable_modify_date = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
// Updates the application state based on the provided `state` parameter.
// This can be a state like "WELCOME", "INTERNET", etc., depending on the condition provided by the caller.
updateState(state);
}
SnigdhaOSBlackbox::~SnigdhaOSBlackbox()
{
// Frees the memory allocated for the user interface (UI) object.
// The 'ui' pointer was allocated in the constructor, and it's responsible for managing the UI components of the SnigdhaOSBlackbox window.
delete ui;
}
void SnigdhaOSBlackbox::doInternetUpRequest() {
// Create a new QNetworkAccessManager instance for managing the network request.
// The 'this' pointer ensures the manager is associated with the current object (SnigdhaOSBlackbox).
QNetworkAccessManager* network_manager = new QNetworkAccessManager(this);
// Send a HEAD request to the specified URL to check if the internet connection is up.
// The HEAD request is used to check the server status without downloading the full content.
QNetworkReply* network_reply = network_manager->head(QNetworkRequest(QString(INTERNET_CHECK_URL)));
// Create a new QTimer instance that will be used to implement a timeout for the network request.
QTimer* timer = new QTimer(this);
// Set the timer to single-shot mode, so it will only trigger once after the specified interval (5 seconds).
timer->setSingleShot(true);
// Start the timer with a 5-second interval (5000 milliseconds).
timer->start(5000); // 5 sec
// Connect the timer's timeout signal to a lambda function.
// This function will execute if the timer expires (i.e., after 5 seconds), aborting the network request.
connect(timer, &QTimer::timeout, this, [this, timer, network_reply, network_manager]() {
// Clean up the resources associated with the timer, network reply, and network manager.
// Deleting them to prevent memory leaks.
timer->deleteLater();
network_reply->abort(); // Abort the network request as the timeout occurred.
network_reply->deleteLater(); // Delete the network reply object to free resources.
network_manager->deleteLater(); // Delete the network manager object to free resources.
// Retry the internet check by calling this function recursively.
doInternetUpRequest();
});
// Connect the network reply's finished signal to a lambda function that handles the network reply.
connect(network_reply, &QNetworkReply::finished, this, [this, timer, network_reply, network_manager]() {
// Stop the timer once the network reply is finished, as we no longer need to wait for the timeout.
timer->stop();
// Clean up the resources associated with the timer, network reply, and network manager after the request finishes.
timer->deleteLater();
network_reply->deleteLater();
network_manager->deleteLater();
// Check if there was no error with the network reply (i.e., the internet is up).
if (network_reply->error() == network_reply->NoError) {
// If there was no error, update the state to indicate that the update process can begin.
updateState(State::UPDATE);
} else {
// If there was an error, retry the internet check by calling this function recursively.
doInternetUpRequest();
}
});
}
void SnigdhaOSBlackbox::doUpdate() {
// Check if the environment variable "SNIGDHAOS_BLACKBOX_SELFUPDATE" is set.
// This is typically used to determine if the application is running in an update process.
if (qEnvironmentVariableIsSet("SNIGDHAOS_BLACKBOX_SELFUPDATE")) {
// If the environment variable is set, update the state to "SELECT" (presumably to indicate a state where the user can select an option).
updateState(State::SELECT);
return; // Exit the function if the self-update process is active.
}
// Create a new QProcess object. This will be used to run external processes (such as the terminal command to update the system).
auto process = new QProcess(this);
// Create a temporary file that will be used during the update process.
QTemporaryFile* file = new QTemporaryFile(this);
file->open(); // Open the temporary file for writing.
file->setAutoRemove(true); // Set the temporary file to be automatically removed when it is closed.
// Start a new process to launch the terminal and execute the system update command.
// The command runs "sudo pacman -Syyu" to update the system and then deletes the temporary file after the update is done.
// It also asks the user to press Enter before closing the terminal.
process->start("/usr/lib/snigdhaos/launch-terminal",
QStringList() << QString("sudo pacman -Syyu 2>&1 && rm \"" + file->fileName() + "\"; read -p 'Press Enter↵ to Exit'"));
// Connect the finished signal of the QProcess to a lambda function, which will be executed when the process finishes.
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, process, file](int exitcode, QProcess::ExitStatus status) {
// Delete the QProcess and temporary file objects after the process finishes.
process->deleteLater();
file->deleteLater();
// Check the exit code of the process:
// If the exit code is 0 (successful), and the temporary file no longer exists,
// it indicates that the update process was successful, so relaunch the app with the state "POST_UPDATE".
if (exitcode == 0 && !file->exists()) {
relaunchSelf("POST_UPDATE");
} else {
// If the update failed (either the exit code is non-zero or the temporary file still exists),
// relaunch the app with the state "UPDATE_RETRY", indicating that the update should be retried.
relaunchSelf("UPDATE_RETRY");
}
});
}
void SnigdhaOSBlackbox::doApply() {
QStringList packages;
QStringList setup_commands;
QStringList prepare_commands;
auto checkBoxList = ui->selectWidget_tabs->findChildren<QCheckBox*>();
for (auto checkbox : checkBoxList) {
if (checkbox->isChecked()) {
packages += checkbox->property("packages").toStringList();
setup_commands += checkbox->property("setup_commands").toStringList();
prepare_commands += checkbox->property("prepare_commands").toStringList();
}
}
if (packages.isEmpty()) {
updateState(State::SUCCESS);
return;
}
if (packages.contains("podman")) {
setup_commands += "systemctl enable --now podman.socket";
}
if (packages.contains("docker")) {
setup_commands += "systemctl enable --now docker.socket";
}
packages.removeDuplicates();
QTemporaryFile* prepareFile = new QTemporaryFile(this);
prepareFile->setAutoRemove(true);
prepareFile->open();
QTextStream prepareStream(prepareFile);
prepareStream << prepare_commands.join('\n');
prepareFile->close();
QTemporaryFile* packagesFile = new QTemporaryFile(this);
packagesFile->setAutoRemove(true);
packagesFile->open();
QTextStream packagesStream(packagesFile);
packagesStream << packages.join(' ');
packagesFile->close();
QTemporaryFile* setupFile = new QTemporaryFile(this);
setupFile->setAutoRemove(true);
setupFile->open();
QTextStream setupStream(setupFile);
setupStream << setup_commands.join('\n');
setupFile->close();
auto process = new QProcess(this);
process->start("/usr/lib/snigdhaos/launch-terminal", QStringList() << QString("/usr/lib/snigdhaos-blackbox/apply.sh \"") + prepareFile->fileName() + "\" \"" + packagesFile->fileName() + "\" \"" + setupFile->fileName() + "\"");
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this, process, prepareFile, packagesFile, setupFile](int exitcode, QProcess::ExitStatus status) {
process->deleteLater();
prepareFile->deleteLater();
packagesFile->deleteLater();
setupFile->deleteLater();
if (exitcode == 0 && !packagesFile->exists()) {
updateState(State::SELECT);
}
else {
updateState(State::APPLY_RETRY);
}
});
}
void SnigdhaOSBlackbox::populateSelectWidget() {
if (ui->selectWidget_tabs->count() > 1) {
return;
}
auto desktop = qEnvironmentVariable("XDG_DESKTOP_SESSION");
ui->checkBox_GNOME->setVisible(desktop == "gnome");
bool isDesktop = false;
auto chassis = QFile("/sys/class/dmi/id/chassis_type");
if (chassis.open(QFile::ReadOnly)) {
QStringList list = { "3", "4", "6", "7", "23", "24" };
QTextStream in(&chassis);
isDesktop = list.contains(in.readLine());
}
ui->checkBox_Performance->setVisible(isDesktop);
populateSelectWidget("/usr/lib/snigdhaos-blackbox/webapp.txt", "WEBAPP");
}
void SnigdhaOSBlackbox::populateSelectWidget(QString filename, QString label) {
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
QScrollArea* scroll = new QScrollArea(ui->selectWidget_tabs);
QWidget* tab = new QWidget(scroll);
QVBoxLayout* layout = new QVBoxLayout(tab);
QTextStream in(&file);
while (!in.atEnd()) {
QString def = in.readLine();
QString packages = in.readLine();
QString display = in.readLine();
auto checkbox = new QCheckBox(tab);
checkbox->setChecked(def == "true");
checkbox->setText(display);
checkbox->setProperty("packages", packages.split(" "));
layout->addWidget(checkbox);
}
scroll->setWidget(tab);
ui->selectWidget_tabs->addTab(scroll, label);
file.close();
}
}
void SnigdhaOSBlackbox::updateState(State state) {
if (currentState != state) {
currentState = state;
this->show();
this->activateWindow();
this->raise();
switch (state) {
case State::WELCOME:
ui->mainStackedWidget->setCurrentWidget(ui->textWidget);
ui->textStackedWidget->setCurrentWidget(ui->textWidget_welcome);
ui->textWidget_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
break;
case State::INTERNET:
ui->mainStackedWidget->setCurrentWidget(ui->mainStackedWidget);
ui->waitingWidget_text->setText("Waiting For Internet Connection...");
doInternetUpRequest();
break;
case State::UPDATE:
ui->mainStackedWidget->setCurrentWidget(ui->waitingWidget);
ui->waitingWidget_text->setText("Please Wait! Till We Finish The Update...");
doUpdate();
break;
case State::UPDATE_RETRY:
ui->mainStackedWidget->setCurrentWidget(ui->textWidget);
ui->textStackedWidget->setCurrentWidget(ui->textWidget_updateRetry);
ui->textWidget_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
break;
case State::QUIT:
ui->mainStackedWidget->setCurrentWidget(ui->textWidget);
ui->textStackedWidget->setCurrentWidget(ui->textWidget_quit);
ui->textWidget_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Reset);
break;
case State::SELECT:
ui->mainStackedWidget->setCurrentWidget(ui->waitingWidget);
populateSelectWidget();
break;
case State::APPLY:
ui->mainStackedWidget->setCurrentWidget(ui->waitingWidget);
ui->waitingWidget_text->setText("We are applying the changes...");
doApply();
break;
case State::APPLY_RETRY:
ui->mainStackedWidget->setCurrentWidget(ui->textWidget);
ui->textStackedWidget->setCurrentWidget(ui->textWidget_applyRetry);
ui->textWidget_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Reset);
break;
case State::SUCCESS:
ui->mainStackedWidget->setCurrentWidget(ui->textWidget);
ui->textStackedWidget->setCurrentWidget(ui->textWidget_success);
ui->textWidget_buttonBox->setStandardButtons(QDialogButtonBox::Ok);
break;
}
}
}
void SnigdhaOSBlackbox::updateState(QString state) {
if (state == "POST_UPDATE"){
updateState(State::SELECT);
}
else if (state == "UPDATE_RETRY") {
updateState(State::UPDATE_RETRY);
}
else {
updateState(State::WELCOME);
}
}
void SnigdhaOSBlackbox::relaunchSelf(QString param) {
auto binary = QFileInfo(QCoreApplication::applicationFilePath());
if (executable_modify_date != binary.lastModified()) {
execlp(binary.absoluteFilePath().toUtf8().constData(), binary.fileName().toUtf8().constData(), param.toUtf8().constData(), NULL);
exit(0);
}
else {
updateState(param);
}
}
void SnigdhaOSBlackbox::on_textWidget_buttonBox_clicked(QAbstractButton* button) {
switch(currentState) {
case State::WELCOME:
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Ok) {
updateState(State::INTERNET);
}
break;
case State::UPDATE_RETRY:
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Yes) {
updateState(State::INTERNET);
}
break;
case State::APPLY_RETRY:
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Yes) {
updateState(State::APPLY);
}
else if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Reset) {
updateState(State::SELECT);
}
break;
case State::SUCCESS:
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Ok) {
QApplication::quit();
}
break;
case State::QUIT:
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::No || ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Ok) {
QApplication::quit();
}
else {
updateState(State::WELCOME);
}
break;
default:;
}
if (ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::No || ui->textWidget_buttonBox->standardButton(button) == QDialogButtonBox::Cancel) {
updateState(State::QUIT);
}
}
void SnigdhaOSBlackbox::on_selectWidget_buttonBox_Clicked(QAbstractButton* button) {
if (ui->selectWidget_buttonBox->standardButton(button) == QDialogButtonBox::Ok) {
updateState(State::APPLY);
}
else {
updateState(State::QUIT);
}
}