[education/rkward/devel/workspace_output] rkward: Start implemnting some basics on the new output handling.

Thomas Friedrichsmeier null at kde.org
Sat Oct 10 09:18:45 BST 2020


Git commit 831eaa85012ff3dcb62377563f39a0962a22b213 by Thomas Friedrichsmeier.
Committed on 06/07/2017 at 20:58.
Pushed by tfry into branch 'devel/workspace_output'.

Start implemnting some basics on the new output handling.

Not much to be seen, yet. General ideas:
- Outputs are imported from their storage locations by copying their folder to a temp location, recursively.
- Directly after importing, a hash is created to detect whether an imported output needs saving.
- Saving is just the reverse: Copy the temp folder to the storage folder.
- Storage folder may be relative to workspace.
- On exit, check all outputs known in that session for changes, prompt for saving.
- Several output folders will be supported, whith actions to create, drop, activate.
- When dropping an output folder, another one is activated automatically (if only one, this corresponds to the current "flush output").

M  +1    -1    rkward/rbackend/rkrinterface.cpp
M  +41   -2    rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R
M  +79   -4    rkward/windows/rkhtmlwindow.cpp
M  +16   -4    rkward/windows/rkhtmlwindow.h

https://invent.kde.org/education/rkward/commit/831eaa85012ff3dcb62377563f39a0962a22b213

diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index ba3a53e0..6995ca9b 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -311,7 +311,7 @@ void RInterface::rCommandDone (RCommand *command) {
 			issueCommand (*it, RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
 		}
 		// initialize output file
-		issueCommand ("rk.set.output.html.file (\"" + RKSettingsModuleGeneral::filesPath () + "/rk_out.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
+		issueCommand ("rk.set.output.html.file (\"" + RKSettingsModuleGeneral::filesPath () + "/output/index.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
 
 		closeChain (chain);
 	} else if (command->getFlags () == GET_R_VERSION) {
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R b/rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R
index f8d3c0da..da4a7564 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R
@@ -8,7 +8,7 @@
 #' 
 #' \code{rk.get.tempfile.name} returns a non-existing filename inside the
 #' specified directory (or the directory of the current output file, if the parameter is
-#' omitted / left empty). The filename is returned as an absolute path,
+#' omitted). The filename is returned as an absolute path,
 #' but the relative path with respect to the base directory can be obtained via
 #' \code{names()}. It is mainly used by \link{rk.graph.on} to
 #' create filenames suitable for storing images in the output. The filenames of
@@ -73,7 +73,7 @@
 #' rk.set.output.html.file(outfile)
 #' 
 #' @export
-"rk.get.tempfile.name" <- function (prefix="image", extension=".jpg", directory="") {
+"rk.get.tempfile.name" <- function (prefix="image", extension=".jpg", directory=dirname (rk.get.output.html.file ())) {
 	x <- .rk.do.simple.call ("unused.filename", c (prefix, extension, directory))
 	ret <- x[2]
 	names (ret) <- x[1]
@@ -105,6 +105,8 @@
 	stopifnot (is.character (x))
 	style <- match.arg (style)
 	oldfile <- rk.get.output.html.file ()
+	dir.create (dirname (x, showWarnings=FALSE, recursive=TRUE))
+	stopifnot (dir.exists (dirname (x)))
 	assign (".rk.output.html.file", x, .rk.variables)
 
 	if (!file.exists (x)) {
@@ -253,3 +255,40 @@
 
 	rk.set.output.html.file (x, ...)
 }
+
+## TODO move the following functions (and some others to a dedicated file/help page)
+# 
+# .rk.purge.target.dir (target, ask) {
+# 	if (dir.exists (target)) {
+# 		if (isTRUE (ask)) {
+# 			if (!rk.show.question (paste ("The target directory (", target, ") already exists, and proceeding will delete everyhting inside this directory. Are you sure you want to proceed?", sep = ""))) {
+# 				stop("Aborted by user")
+# 			}
+# 		}
+# 		try (target, recursive=TRUE)
+# 	}
+# 	stopifnot (dir.create (target.dir, showWarnings=FALSE, recursive=TRUE))
+# }
+
+#' @export
+#' @rdname rk.get.tempfile.name
+rk.export.output.dir <- function (target.dir, source.dir=basename (rk.get.output.html.file ()), ask=TRUE) {
+# This is not terribly complex, but we need an implementation in the frontend, anyway, so we use that.
+	x <- .rk.plain.call ("output.export", c (target.dir, source.dir, as.character (isTRUE (ask))))
+	if (!is.null (x)) stop (x)
+# 	.rk.purge.target.dir (target.dir, ask)
+# 	file.copy (source.dir, target.dir, recursive = TRUE, copy.date = TRUE)
+}
+
+#' @export
+#' @rdname rk.get.tempfile.name
+rk.import.output.dir <- function (source.dir, target.dir=file.path (rk.tempdir (), "output"), ask=TRUE, activate="index.html") {
+# This is not terribly complex, but we need an implementation in the frontend, anyway, so we use that.
+	x <- .rk.plain.call ("output.import", c (target.dir, source.dir, as.character (isTRUE (ask)), activate))
+	if (!is.null (x)) stop (x)
+# 	.rk.purge.target.dir (target.dir, ask)
+# 	file.copy (source.dir, target.dir, recursive = TRUE, copy.date = TRUE)
+# 	if (nzchar (activate)) {
+# 		rk.set.output.html.file (file.path (target.dir, activate))
+# 	}
+}
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 1fd2fecb..3533303a 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -988,6 +988,10 @@ void RKHelpRenderer::writeHTML (const QString& string) {
 /////////////////////////////////////
 /////////////////////////////////////
 
+#include <QDir>
+#include <QFileInfo>
+#include <QCryptographicHash>
+#include <KIO/CopyJob>
 
 // static
 RKOutputWindowManager* RKOutputWindowManager::_self = 0;
@@ -1093,18 +1097,22 @@ void RKOutputWindowManager::rewatchOutput () {
 	file_watcher->addFile (current_default_path);
 }
 
-QList<RKHTMLWindow*> RKOutputWindowManager::existingOutputWindows () const {
+QList<RKHTMLWindow*> RKOutputWindowManager::existingOutputWindows (const QString &path) const {
 	RK_TRACE (APP);
 
+	if (!path.isNull ()) return (windows.values (path));
 	return (windows.values (current_default_path));
 }
 
-RKHTMLWindow* RKOutputWindowManager::newOutputWindow () {
+RKHTMLWindow* RKOutputWindowManager::newOutputWindow (const QString& _path) {
 	RK_TRACE (APP);
 
+	QString path = _path;
+	if (path.isNull ()) path = current_default_path;
+
 	RKHTMLWindow* current_output = new RKHTMLWindow (RKWorkplace::mainWorkplace ()->view (), RKHTMLWindow::HTMLOutputWindow);
-	current_output->openURL (QUrl::fromLocalFile (current_default_path));
-	RK_ASSERT (current_output->url ().toLocalFile () == current_default_path);
+	current_output->openURL (QUrl::fromLocalFile (path));
+	RK_ASSERT (current_output->url ().toLocalFile () == path);
 
 	return current_output;
 }
@@ -1144,3 +1152,70 @@ void RKOutputWindowManager::windowDestroyed (QObject *window) {
 	}
 }
 
+/** much like `ls -Rl`. List directory contents including timestamps and sizes, recursively. Used to detect whether an output directory has any changes. */
+void listDirectoryState (const QString& _dir, QString *list) {
+	RK_TRACE (APP);
+
+	QDir dir (_dir);
+	QFileInfoList entries = dir.entryInfoList (QDir::NoFilter, QDir::Name | QDir::DirsLast);
+	for (int i = 0; i < entries.size (); ++i) {
+		const QFileInfo fi = entries[i];
+		if (fi.isDir ()) {
+			list->append (QStringLiteral ("dir: ") + fi.fileName () + '\n');
+			listDirectoryState (fi.absolutePath (), list);
+			list->append (QStringLiteral ("enddir\n"));
+		} else {
+			list->append (fi.fileName () + '\n');
+			list->append (fi.lastModified ().toString ("dd.hh.mm.ss.zzz") + '\n');
+			list->append (QString::number (fi.size ()) + '\n');
+		}
+	}
+}
+
+QString hashDirectoryState (const QString& dir) {
+	RK_TRACE (APP);
+	QString list;
+	listDirectoryState (dir, &list);
+	return QCryptographicHash::hash (list.toUtf8 (), QCryptographicHash::Md5);
+}
+
+QString RKOutputWindowManager::importOutputDirectory (const QString& _dir, const QString& index_file) {
+	RK_TRACE (APP);
+
+	QFileInfo fi (_dir);
+	QString dir = fi.canonicalFilePath ();
+	for (int i = 0; i < outputs.count (); ++i) {
+		if (outputs[i].save_dir == dir) {
+			if (KMessageBox::warningContinueCancel (RKWardMainWindow::getMain (), i18n ("The output directory %1 is already imported (use Window->Show Output to show it). Importing it again will revert any unsaved changes made to the output directory during the past %2 minutes. Are you sure you want to revert?", dir, outputs[i].save_timestamp.secsTo (QDateTime::currentDateTime ()) / 60), i18n ("Reset output directory?"), KStandardGuiItem::reset ()) != KMessageBox::Continue) {
+				return QString ();
+			} else {
+				outputs.removeAt (i);
+				break;
+			}
+		}
+	}
+
+	QString prefix = QStringLiteral ("unsaved_output");
+	QString destname = prefix;
+	QDir ddir (RKSettingsModuleGeneral::filesPath ());
+	int i = 0;
+	while (true) {
+		if (!ddir.exists (destname)) break;
+		destname = prefix + QString::number (i);
+	}
+	QString dest = ddir.absoluteFilePath (destname);
+
+	KIO::CopyJob *j = KIO::copyAs (QUrl::fromLocalFile (dir), QUrl::fromLocalFile (dest));
+#warning No good, just for testing.
+	j->exec ();
+
+	OutputDirectory od;
+	od.index_file = index_file;
+	od.save_dir = dir;
+	od.work_dir = dest;
+	od.saved_hash = hashDirectoryState (dest);
+	od.save_timestamp = QDateTime::currentDateTime ();
+	outputs.append (od);
+
+	return (dest + '/' + index_file);
+}
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 0a0a63e5..29b19d4a 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -221,10 +221,13 @@ public:
 	void registerWindow (RKHTMLWindow *window);
 /** R may produce output while no output window is active. This allows to set the file that should be monitored for such changes (called from within rk.set.html.output.file()). */
 	void setCurrentOutputPath (const QString &path);
-/** returns a list (possibly empty) of pointers to existing output windows (for the current output path, only). */
-	QList<RKHTMLWindow*> existingOutputWindows () const;
-/** Create (and show) a new output window, and @return the pointer */
-	RKHTMLWindow* newOutputWindow ();
+/** returns a list (possibly empty) of pointers to existing output windows for the given path (for the current output path, if no path given). */
+	QList<RKHTMLWindow*> existingOutputWindows (const QString &path = QString ()) const;
+/** Create (and show) a new output window (for the current output path, unless path is specified), and @return the pointer */
+	RKHTMLWindow* newOutputWindow (const QString& path = QString ());
+/** Import an existing output directory. @Returns the file name of the index_file in the writeable location (the "output path"), or an empty string, if an error occured. */
+	QString importOutputDirectory (const QString& dir, const QString& index_file);
+	QString createOutputDirectory ();
 private:
 	RKOutputWindowManager ();
 	~RKOutputWindowManager ();
@@ -233,6 +236,15 @@ private:
 	QString current_default_path;
 	KDirWatch *file_watcher;
 	QMultiHash<QString, RKHTMLWindow *> windows;
+
+	struct OutputDirectory {
+		QString work_dir;
+		QString index_file;
+		QString saved_hash;
+		QDateTime save_timestamp;
+		QString save_dir;
+	};
+	QList<OutputDirectory> outputs;
 private slots:
 	void fileChanged (const QString &path);
 	void windowDestroyed (QObject *window);




More information about the rkward-tracker mailing list