[education/rkward] /: Use inline feedback for package installation, as well. This still needs some work on the details (closing of dialog / cancel / progress feedback), but works in principle.
Thomas Friedrichsmeier
null at kde.org
Sun May 1 21:37:49 BST 2022
Git commit d49d07cd3ba791e08baefde791ad1b1eab7d9e5f by Thomas Friedrichsmeier.
Committed on 30/04/2022 at 21:55.
Pushed by tfry into branch 'master'.
Use inline feedback for package installation, as well. This still needs some work on the details (closing of dialog / cancel / progress feedback), but works in principle.
M +4 -0 ChangeLog
M +1 -0 rkward/dialogs/CMakeLists.txt
M +30 -65 rkward/dialogs/rkloadlibsdialog.cpp
M +0 -7 rkward/dialogs/rkloadlibsdialog.h
M +23 -17 rkward/misc/rkprogresscontrol.cpp
M +2 -2 rkward/misc/rkprogresscontrol.h
https://invent.kde.org/education/rkward/commit/d49d07cd3ba791e08baefde791ad1b1eab7d9e5f
diff --git a/ChangeLog b/ChangeLog
index 084d37d4..c8950a2f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,10 @@
TODOs:
- More tolerant handshake on Windows? Simply a matter of allowing more time?
+- Package installation no longer uses an external process, unless required for root permissions
+ - TODO: Trim down required commands, show what's left, inline
+ - TODO: swallow installation output in require()
+- Fixed: Package installation as root would fail to find kdesu/kdesudo on recent systems
- Fixed: R help pages would refuse to open in new tab
- (Re-)added improved "Import Assistant" to help getting started with importing data
- "Paste special" can now also paste data.frames with labels, and is available in the Workspace Browser context menu
diff --git a/rkward/dialogs/CMakeLists.txt b/rkward/dialogs/CMakeLists.txt
index bcf786b6..5e09bf6b 100644
--- a/rkward/dialogs/CMakeLists.txt
+++ b/rkward/dialogs/CMakeLists.txt
@@ -11,5 +11,6 @@ SET(dialogs_STAT_SRCS
rksetupwizard.cpp
)
+ADD_DEFINITIONS(-DLIBEXECDIR="${CMAKE_INSTALL_FULL_LIBEXECDIR}")
ADD_LIBRARY(dialogs STATIC ${dialogs_STAT_SRCS})
TARGET_LINK_LIBRARIES(dialogs Qt5::Widgets KF5::Parts KF5::ConfigWidgets KF5::TextEditor)
diff --git a/rkward/dialogs/rkloadlibsdialog.cpp b/rkward/dialogs/rkloadlibsdialog.cpp
index b862105f..60bfcf85 100644
--- a/rkward/dialogs/rkloadlibsdialog.cpp
+++ b/rkward/dialogs/rkloadlibsdialog.cpp
@@ -306,86 +306,51 @@ bool RKLoadLibsDialog::installPackages (const QStringList &packages, QString to_
void RKLoadLibsDialog::runInstallationCommand (const QString& command, bool as_root, const QString& message, const QString& title) {
RK_TRACE (DIALOGS);
- // TODO: won't work with some versions of GCC (which ones exactly)?
-// QFile file (QDir (RKSettingsModuleGeneral::filesPath ()).filePath ("install_script.R"));
-// WORKAROUND:
- QDir dir = RKSettingsModuleGeneral::filesPath ();
- QFile file (dir.filePath ("install_script.R"));
-// WORKADOUND END
+ QFile file(QDir(RKSettingsModuleGeneral::filesPath()).filePath("install_script.R"));
if (file.open (QIODevice::WriteOnly)) {
QTextStream stream (&file);
stream << command;
- stream << "q ()\n";
+ if (as_root) stream << "q()\n";
file.close();
} else {
RK_ASSERT (false);
}
- QString call;
- QStringList params;
-#ifdef Q_OS_WIN
- RK_ASSERT (!as_root);
- call = RKSessionVars::RBinary();
-#else
+ auto control = new RKInlineProgressControl(install_packages_widget, true);
+ control->messageWidget()->setText(QString("<b>%1</b><p>%2</p>").arg(title, message));
+ control->show();
+
+ RCommand *rcommand;
if (as_root) {
- call = QStandardPaths::findExecutable ("kdesu");
- if (call.isEmpty ()) call = QStandardPaths::findExecutable ("kdesudo");
- params << "-t" << "--" << RKSessionVars::RBinary();
+ QStringList libexecpath(LIBEXECDIR "/kf5");
+ libexecpath << QString(LIBEXECDIR "/kf6");
+ QString call = QStandardPaths::findExecutable("kdesu");
+ if (call.isEmpty()) call = QStandardPaths::findExecutable("kdesu", libexecpath);
+ if (call.isEmpty ()) call = QStandardPaths::findExecutable("kdesudo");
+ if (call.isEmpty ()) call = QStandardPaths::findExecutable("kdesudo", libexecpath);
+ if (call.isEmpty()) {
+ KMessageBox::sorry(this, i18n("Neither kdesu nor kdesudo could be found. To install as root, please install one of these packages."), i18n("kdesu not found"));
+ file.remove();
+ return;
+ }
+ QStringList call_with_params(call);
+ call_with_params << "-t" << "--" << RKSessionVars::RBinary() << "--no-save" << "--no-restore" << "--file=" + file.fileName();
+ rcommand = new RCommand(QString("system(%1)").arg(RObject::rQuote(call_with_params.join(" "))), RCommand::App);
} else {
- call = RKSessionVars::RBinary();
+ rcommand = new RCommand(QString("source(%1, local=new.env())").arg(RObject::rQuote(file.fileName())), RCommand::App);
}
-#endif
- params << "--no-save" << "--no-restore" << "--file=" + file.fileName ();
-
- installation_process = new QProcess ();
- installation_process->setProcessChannelMode (QProcess::SeparateChannels);
-
- connect (installation_process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &RKLoadLibsDialog::processExited);
- connect (installation_process, &QProcess::readyReadStandardOutput, this, &RKLoadLibsDialog::installationProcessOutput);
- connect (installation_process, &QProcess::readyReadStandardError, this, &RKLoadLibsDialog::installationProcessError);
-
- RKProgressControl *installation_progress = new RKProgressControl (this, message, title, RKProgressControl::CancellableProgress);
- connect (this, &RKLoadLibsDialog::installationComplete, installation_progress, &RKProgressControl::done);
- connect (this, &RKLoadLibsDialog::installationOutput, installation_progress, static_cast<void (RKProgressControl::*)(const QString&)>(&RKProgressControl::newOutput));
- connect (this, &RKLoadLibsDialog::installationError, installation_progress, &RKProgressControl::newError);
-
- installation_process->start (call, params, QIODevice::ReadWrite | QIODevice::Unbuffered);
-
- if (!installation_progress->doModal (true)) {
- installation_process->kill ();
- installation_process->waitForFinished (5000);
+ control->addRCommand(rcommand);
+ RInterface::issueCommand(rcommand, chain);
+ bool done = false;
+ connect(rcommand->notifier(), &RCommandNotifier::commandFinished, this, [&done]() { done = true; });
+ while (!done) {
+ QApplication::processEvents();
}
- file.remove ();
- delete installation_process;
- installation_process = 0;
+ file.remove();
emit installedPackagesChanged();
}
-void RKLoadLibsDialog::installationProcessOutput () {
- RK_TRACE (DIALOGS);
- RK_ASSERT (installation_process);
-
- emit installationOutput(QString::fromLocal8Bit(installation_process->readAllStandardOutput()));
-}
-
-void RKLoadLibsDialog::installationProcessError () {
- RK_TRACE (DIALOGS);
- RK_ASSERT (installation_process);
-
- emit installationError(QString::fromLocal8Bit(installation_process->readAllStandardError()));
-}
-
-void RKLoadLibsDialog::processExited (int exitCode, QProcess::ExitStatus exitStatus) {
- RK_TRACE (DIALOGS);
-
- if (exitCode || (exitStatus != QProcess::NormalExit)) {
- emit installationError('\n' + i18n ("Installation process died with exit code %1", exitCode));
- }
-
- emit installationComplete();
-}
-
////////////////////// LoadUnloadWidget ////////////////////////////
LoadUnloadWidget::LoadUnloadWidget (RKLoadLibsDialog *dialog) : QWidget (0) {
@@ -935,7 +900,7 @@ void RKRPackageInstallationStatus::initialize (RCommandChain *chain) {
RKInlineProgressControl *control = new RKInlineProgressControl(display_area, true);
control->messageWidget()->setText(i18n("<p>Please stand by while searching for installed and available packages.</p><p><strong>Note:</strong> This requires a working internet connection, and may take some time, esp. if one or more repositories are temporarily unavailable.</p>"));
control->addRCommand(command);
- //control->setAutoCloseWhenCommandsDone(true);
+ control->setAutoCloseWhenCommandsDone(true);
RInterface::issueCommand(command, chain);
control->show(100);
}
diff --git a/rkward/dialogs/rkloadlibsdialog.h b/rkward/dialogs/rkloadlibsdialog.h
index cdf29c5f..93a2456a 100644
--- a/rkward/dialogs/rkloadlibsdialog.h
+++ b/rkward/dialogs/rkloadlibsdialog.h
@@ -59,19 +59,12 @@ public:
void accept () override;
void reject () override;
signals:
- void downloadComplete ();
- void installationComplete ();
void libraryLocationsChanged (const QStringList &liblocs);
- void installationOutput (const QString &output);
- void installationError (const QString &error);
void installedPackagesChanged ();
protected:
void closeEvent (QCloseEvent *e) override;
protected slots:
void childDeleted ();
- void processExited (int exitCode, QProcess::ExitStatus exitStatus);
- void installationProcessOutput ();
- void installationProcessError ();
void automatedInstall (const QStringList &packages);
void slotPageChanged ();
private:
diff --git a/rkward/misc/rkprogresscontrol.cpp b/rkward/misc/rkprogresscontrol.cpp
index 839ab119..dfed16ad 100644
--- a/rkward/misc/rkprogresscontrol.cpp
+++ b/rkward/misc/rkprogresscontrol.cpp
@@ -139,12 +139,6 @@ void RKProgressControl::newOutput (const QString &output) {
if (dialog) dialog->addOutput (&outputc);
}
-void RKProgressControl::resetOutput () {
- RK_TRACE (MISC);
-
- output_log.clear ();
-}
-
void RKProgressControl::addRCommand (RCommand *command, bool done_when_finished) {
RK_TRACE (MISC);
RK_ASSERT (command);
@@ -395,7 +389,9 @@ RKInlineProgressControl::RKInlineProgressControl(QWidget *display_area, bool all
display_area->window()->installEventFilter(this);
wrapper = new QWidget(display_area);
+ wrapper->setAutoFillBackground(true);
auto layout = new QVBoxLayout(wrapper);
+ layout->setContentsMargins(0,0,0,0);
message_widget = new KMessageWidget();
message_widget->setWordWrap(true);
message_widget->setCloseButtonVisible(false); // we want a button, instead, for consistency with cancel
@@ -438,23 +434,33 @@ void RKInlineProgressControl::addRCommand(RCommand *command) {
message_widget->setMessageType(KMessageWidget::Error);
}
if (unfinished_commands.isEmpty()) {
- if (autoclose) {
- deleteLater();
- } else {
- setCloseAction(i18n("Close"));
- }
+ done();
}
});
connect(command->notifier(), &RCommandNotifier:: commandOutput, this, [this](RCommand *, const ROutput *o) {
- if (o->type == ROutput::Output) {
- output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NormalText).color());
- } else {
- output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NegativeText).color());
- }
- output_display->insertPlainText(o->output);
+ addOutput(o->output, o->type != ROutput::Output);
});
}
+void RKInlineProgressControl::addOutput(const QString& output, bool is_error_warning) {
+ RK_TRACE(MISC);
+ if (is_error_warning) {
+ output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NegativeText).color());
+ } else {
+ output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NormalText).color());
+ }
+ output_display->insertPlainText(output);
+}
+
+void RKInlineProgressControl::done() {
+ RK_TRACE(MISC);
+ if (autoclose) {
+ deleteLater();
+ } else {
+ setCloseAction(i18n("Close"));
+ }
+}
+
void RKInlineProgressControl::show(int delay_ms) {
RK_TRACE(MISC);
if (delay_ms > 0) {
diff --git a/rkward/misc/rkprogresscontrol.h b/rkward/misc/rkprogresscontrol.h
index 9ee1d7d1..52c028d2 100644
--- a/rkward/misc/rkprogresscontrol.h
+++ b/rkward/misc/rkprogresscontrol.h
@@ -64,8 +64,6 @@ public:
/** initialize the dialog non modal. The dialog is only shown if needed or set in the constructor flags */
void doNonModal (bool autodelete);
-/** you don't need this, unless you feed regular output to the dialog using newOutput. */
- void resetOutput ();
/** add a command to listen to. Warning: You will always first call addRCommand, then submit the command to RInterface, never the other way around. Else there could be a race condition!
@param done_when_finished If set to true, the done () -slot is auto-called when the given command has completed */
void addRCommand (RCommand *command, bool done_when_finished=false);
@@ -111,6 +109,8 @@ public:
void setAutoCloseWhenCommandsDone(bool _autoclose) { autoclose = _autoclose; };
void show(int delay_ms=0);
KMessageWidget *messageWidget() { return message_widget; };
+ void addOutput(const QString &output, bool is_error_warning);
+ void done();
private:
void setCloseAction(const QString &label);
bool eventFilter(QObject *, QEvent *e) override;
More information about the rkward-tracker
mailing list