[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