[rkward/work/workspace_output] rkward: Start implemnting some basics on the new output handling.
Thomas Friedrichsmeier
null at kde.org
Thu Jul 6 21:06:10 UTC 2017
Git commit 831eaa85012ff3dcb62377563f39a0962a22b213 by Thomas Friedrichsmeier.
Committed on 06/07/2017 at 20:58.
Pushed by tfry into branch 'work/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://commits.kde.org/rkward/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