[education/rkward/devel/workspace_output] rkward: More work-in-progress on output directories
Thomas Friedrichsmeier
null at kde.org
Sun Oct 18 09:50:34 BST 2020
Git commit 6289dd50b24b7d8f1ca9080c7e8e16c4af31b1e1 by Thomas Friedrichsmeier.
Committed on 18/10/2020 at 08:50.
Pushed by tfry into branch 'devel/workspace_output'.
More work-in-progress on output directories
M +95 -106 rkward/misc/rkoutputdirectory.cpp
M +15 -11 rkward/misc/rkoutputdirectory.h
M +1 -1 rkward/rbackend/rpackages/rkward/R/rk.output.R
https://invent.kde.org/education/rkward/commit/6289dd50b24b7d8f1ca9080c7e8e16c4af31b1e1
diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index b1c6ef3d..8c020e50 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -21,8 +21,15 @@
#include <QFileInfo>
#include <QFileDialog>
#include <KLocalizedString>
+
#include <KMessageBox>
+#include "../settings/rksettingsmodulegeneral.h"
+#include "../rbackend/rcommand.h"
+#include "../misc/rkcommonfunctions.h"
+#include "../rkglobals.h"
+#include "../rkward.h"
+
#include "../debug.h"
/** much like `ls -Rl`. List directory contents including timestamps and sizes, recursively. Used to detect whether an output directory has any changes. */
@@ -74,7 +81,7 @@ bool copyDirRecursively(const QString& _source_dir, const QString& _dest_dir) {
return ok;
}
-RKOutputDirectory::RKOutputDirectory() {
+RKOutputDirectory::RKOutputDirectory() : initialized(false), window(nullptr) {
RK_TRACE(APP);
}
@@ -82,7 +89,7 @@ RKOutputDirectory::RKOutputDirectory() {
RK_TRACE(APP);
}
-RKOutputDirectory* RKOutputDirectory::getOpenDirectory(const QString id, GenericRCallResult* result) {
+RKOutputDirectory* RKOutputDirectory::getOutputById(const QString& id, GenericRCallResult* result) {
RK_TRACE (APP);
RK_ASSERT (result);
@@ -90,15 +97,38 @@ RKOutputDirectory* RKOutputDirectory::getOpenDirectory(const QString id, Generic
result->error = i18n("The output identified by '%1' is not loaded in this session.", id);
return 0;
}
- return &outputs[id];
+ return outputs[id];
+}
+
+RKOutputDirectory* RKOutputDirectory::getOutputBySaveUrl(const QString& _dest, bool create) {
+ RK_TRACE (APP);
+
+ if (_dest.isEmpty()) {
+ if (!create) return getActiveOutput();
+ return createOutputDirectoryInternal();
+ }
+
+ QString dest = QFileInfo(_dest).canonicalFilePath();
+
+ auto it = outputs.constBegin();
+ while (it != outputs.constEnd()) {
+ if (it.value()->save_dir == dest) {
+ return(it.value());
+ }
+ }
+
+# error move to handleRCall
+ // not returned, yet? We need to create and import an output
+ auto ret = createOutputDirectoryInternal();
+ ret->import(_dest);
+ return ret;
}
RKOutputDirectory::GenericRCallResult RKOutputDirectory::save(const QString& dest, RKOutputDirectory::OverwriteBehavior overwrite) {
RK_TRACE (APP);
GenericRCallResult res = exportAs(dest, overwrite);
if (!res.failed()) {
- save_timestamp = QDateTime::currentDateTime();
- saved_hash = hashDirectoryState(work_dir);
+ updateSavedHash();
save_dir = res.ret.toString(); // might by different from dest, notably, if dest was empty
}
return res;
@@ -125,6 +155,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
// If destination already exists, rename it before copying, so we can restore the save in case of an error
QString tempname;
+ dest = QFileInfo(dest).canonicalFilePath();
bool exists = QFileInfo(dest).exists();
if (exists) {
#warning TODO Check terminology ("output directory") once finalized
@@ -166,124 +197,86 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
return GenericRCallResult(QVariant(dest)); // return effective destination path. Needed by save()
}
-
-
-
-
-
-QString RKOutputDirectory::importOutputDirectory (const QString& _dir, const QString& _index_file, bool ask_revert, RCommandChain* chain) {
+RKOutputDirectory::GenericRCallResult RKOutputDirectory::import(const QString& _dir) {
RK_TRACE (APP);
- QFileInfo fi (_dir);
- if (!fi.isDir ()) {
- return i18n ("The path %1 does not exist or is not a directory.", _dir);
+ if (initialized) {
+ return GenericRCallResult::makeError(i18n("Output directory %1 is already in use.", work_dir));
}
-
- QString dir = fi.canonicalFilePath ();
- QMap<QString, OutputDirectory>::const_iterator i = outputs.constBegin ();
- while (i != outputs.constEnd ()) {
- if (i.value ().save_dir == dir) {
- if (ask_revert && (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, i.value ().save_timestamp.secsTo (QDateTime::currentDateTime ()) / 60), i18n ("Reset output directory?"), KStandardGuiItem::reset ()) != KMessageBox::Continue)) {
- return i18n ("Cancelled");
- } else {
- outputs.remove (i.key ());
- break;
- }
- }
+ QFileInfo fi(_dir);
+ if (!fi.isDir()) {
+ return GenericRCallResult::makeError(i18n("The path %1 does not exist or is not a directory.", _dir));
}
+ QString dir = fi.canonicalFilePath ();
- QString index_file = _index_file;
- if (index_file.isEmpty ()) {
- QStringList html_files = QDir (dir).entryList (QStringList () << "*.html" << "*.htm", QDir::Files | QDir::Readable);
- if (html_files.isEmpty ()) {
- return i18n ("The directory %1 does not appear to contain any (readable) HTML-files.", dir);
- } else if (html_files.contains ("rk_out.html")) {
- index_file = "rk_out.html";
- } else if (html_files.contains ("index.html")) {
- index_file = "index.html";
- } else {
- index_file = html_files.first ();
- }
+ if (!copyDirRecursively(dir, work_dir)) {
+ QDir(work_dir).removeRecursively();
+ return GenericRCallResult::makeError(i18n("The path %1 could not be imported (copy failure).", _dir));
}
- bool was_rkward_ouput = isRKWwardOutputDirectory (dir);
- if ((!was_rkward_ouput) && ask_revert) {
- if (KMessageBox::warningContinueCancel (RKWardMainWindow::getMain (), i18n ("The directory %1 does not appear to be an existing RKWard output directory. RKWard can try to import it, anyway (and this will usually work for output files created by RKWard < 0.7.0), but it may not be possible to append to the existing output, and for safety concerns RKWard will refuse to save the modified output back to this directory.\nAre you sure you want to continue?", dir)) != KMessageBox::Continue) {
- return i18n ("Cancelled");
- }
- }
+ save_dir = dir;
+ updateSavedHash();
+ initialized = true;
+
+ return GenericRCallResult(QVariant(id));
+}
- QString dest = createOutputDirectoryInternal ();
+RKOutputDirectory* RKOutputDirectory::createOutputDirectoryInternal() {
+ RK_TRACE (APP);
- if (!copyDirRecursively (dir, dest)) {
- QDir (dest).removeRecursively ();
- return i18n ("The path %1 could not be imported (copy failure).", _dir);
+ QString prefix = QStringLiteral("unsaved_output");
+ QString destname = prefix;
+ QDir ddir(RKSettingsModuleGeneral::filesPath());
+ int x = 0;
+ while (ddir.exists(destname)) {
+ destname = prefix + QString::number(x++);
}
+ ddir.mkpath(destname);
+
+ QFile marker(destname + QStringLiteral("/rkward_output_marker.txt"));
+ marker.open(QIODevice::WriteOnly);
+ marker.write(i18n("This file is used to indicate that this directory is an ouptut directory created by RKWard (http://rkward.kde.org). Do not place any other contents in this directory, as the entire directory will be purged if/when overwriting the output.\nRKWard will ask you before purging this directory (unless explicitly told otherwise), but if you remove this file, RKWard will not purge this directory.\n").toLocal8Bit());
+ marker.close();
+
+ auto d = new RKOutputDirectory();
+ d->work_dir = QFileInfo(ddir.absoluteFilePAth(destname)).canonicalFilePath();
+ d->id = d->work_dir;
+ d->initialized = false;
+ outputs.insert(d->id, d);
+ return d;
+}
- OutputDirectory od;
- od.index_file = index_file;
- if (was_rkward_ouput) od.save_dir = dir;
- od.saved_hash = hashDirectoryState (dest);
- od.save_timestamp = QDateTime::currentDateTime ();
- outputs.insert (dest, od);
+RKOutputDirectory::GenericRCallResult RKOutputDirectory::activate(RCommandChain* chain) {
+ RK_TRACE (APP);
- backendActivateOutputDirectory (dest, chain);
+ QString index_file = work_dir + "/index.html";
+ RKGlobals::rInterface()->issueCommand(QStringLiteral("rk.set.output.html.file(\"") + RKCommonFunctions::escape(index_file) + QStringLiteral("\")\n"), RCommand::App, QString(), 0, 0, chain);
+ if(!initialized) {
+ // when an output directory is first initialized, we don't want that to count as a "modification". Therefore, update the "saved hash" _after_ initialization
+ RCommand *command = new RCommand(QString(), RCommand::App | RCommand::Sync | RCommand::EmptyCommand);
+ connect (command->notifier(), &RCommandNotifier::commandFinished, this, &RKOutputDirectory::updateSavedHash);
+ RKGlobals::rInterface()->issueCommand(command, chain);
+ initialized = true;
+ }
- return (QString ());
+ return GenericRCallResult(QVariant(index_file));
}
-QString RKOutputDirectory::createOutputDirectory (RCommandChain* chain) {
- RK_TRACE (APP);
-
-id= const QString work_dir = QFileInfo (dir).canonicalFilePath ();
- QString dest = createOutputDirectoryInternal ();
- OutputDirectory od;
- od.index_file = QStringLiteral ("index.html");
- outputs.insert (dest, od);
- backendActivateOutputDirectory (dest, chain);
- // the state of the output directory cannot be hashed until the backend has created the index file (via backendActivateOutputDirectory())
- RCommand *command = new RCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand);
- command->notifier ()->setProperty ("path", dest);
- connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKOutputWindowManager::updateOutputSavedHash);
- RKGlobals::rInterface ()->issueCommand (command, chain);
- return dest;
-}
-QString RKOutputDirectory::createOutputDirectoryInternal () {
- RK_TRACE (APP);
- QString prefix = QStringLiteral ("unsaved_output");
- QString destname = prefix;
- QDir ddir (RKSettingsModuleGeneral::filesPath ());
- int x = 0;
- while (true) {
- if (!ddir.exists (destname)) break;
- destname = prefix + QString::number (x++);
- }
- ddir.mkpath (destname);
- QFile marker (destname + QStringLiteral ("/rkward_output_marker.txt"));
- marker.open (QIODevice::WriteOnly);
- marker.write (i18n ("This file is used to indicate that this directory is an ouptut directory created by RKWard (http://rkward.kde.org). Do not place any other contents in this directory, as the entire directory will be purged if/when overwriting the output.\nRKWard will ask you before purging this directory (unless explicitly told otherwise), but if you remove this file, RKWard will not purge this directory.\n").toLocal8Bit ());
- marker.close ();
- return ddir.absoluteFilePath (destname);
-}
+
QString RKOutputDirectory::dropOutputDirectory (const QString& dir, bool ask, RCommandChain* chain) {
RK_TRACE (APP);
- const QString work_dir = QFileInfo (dir).canonicalFilePath ();
- if (!outputs.contains (work_dir)) {
- return i18n ("The directory %1 does not correspond to to any output directory loaded in this session.", dir);
- }
-
OutputDirectory od = outputs[work_dir];
if (ask) {
if (od.saved_hash != hashDirectoryState (work_dir)) {
@@ -359,25 +352,21 @@ QString RKOutputDirectory::outputCaption (const QString& dir) const {
bool RKOutputDirectory::isRKWwardOutputDirectory (const QString& dir) {
RK_TRACE (APP);
- return (QDir (dir).exists (QStringLiteral ("rkward_output_marker.txt")));
+ return (QDir(dir).exists(QStringLiteral("rkward_output_marker.txt")));
}
bool RKOutputDirectory::isOutputDirectoryModified(const QString dir) {
RK_TRACE (APP);
-}
-
-void RKOutputDirectory::backendActivateOutputDirectory (const QString& dir, RCommandChain* chain) {
- RK_TRACE (APP);
- RKGlobals::rInterface ()->issueCommand (QStringLiteral ("rk.set.output.html.file (\"") + RKCommonFunctions::escape (dir + '/' + outputs.value (dir).index_file) + QStringLiteral ("\")\n"), RCommand::App, QString (), 0, 0, chain);
+#warning TODO
+ return true;
}
-void RKOutputDirectory::updateOutputSavedHash (RCommand* command) {
+void RKOutputDirectory::updateSavedHash () {
RK_TRACE (APP);
- QString path = command->notifier ()->property ("path").toString ();
- RK_ASSERT (outputs.contains (path));
- OutputDirectory &output = outputs[path];
- output.saved_hash = hashDirectoryState (path);
- output.save_timestamp = QDateTime::currentDateTime ();
+ saved_hash = hashDirectoryState(work_dir);
+ save_timestamp = QDateTime::currentDateTime();
}
+
+#include "rkoutputdirectory.moc"
diff --git a/rkward/misc/rkoutputdirectory.h b/rkward/misc/rkoutputdirectory.h
index 5f1a6b86..4bd784de 100644
--- a/rkward/misc/rkoutputdirectory.h
+++ b/rkward/misc/rkoutputdirectory.h
@@ -22,11 +22,12 @@
#include <QUrl>
#include <QVariant>
#include <QDateTime>
+#include <QPointer>
+#include <QObject>
-class RKOutputDirectory {
+class RKOutputDirectory : public QObject {
+ Q_OBJECT
public:
- RKOutputDirectory();
- ~RKOutputDirectory();
#warning TODO: Move me to backend
/** Standard wrapper for the result of a "generic" API call, such that we need less special-casing in the backend. The conventions are simple:
* - If @param error is non-null, stop() will be called in the backend, with the given message.
@@ -49,10 +50,8 @@ public:
Ask,
Fail
};
- void activate(RCommandChain* chain=0);
- bool isEmpty();
+ GenericRCallResult activate(RCommandChain* chain=0);
GenericRCallResult revert(bool ask);
- GenericRCallResult createOrImport(const QUrl from);
GenericRCallResult save(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
GenericRCallResult exportAs(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
GenericRCallResult clear(OverwriteBehavior discard=Ask);
@@ -62,23 +61,28 @@ public:
QString filename();
QString workDir();
static GenericRCallResult handleRCall(const QStringList& params);
- static RKOutputDirectory* getOpenDirectory(const QString id, GenericCallResult* result);
+ static RKOutputDirectory* getOutputById(const QString& id, GenericCallResult* result);
+ static RKOutputDirectory* getOutputBySaveUrl(const QString& dest, bool create=false);
private:
+ RKOutputDirectory();
+ ~RKOutputDirectory();
+ void updateSavedHash();
+
QString saved_hash;
QDateTime save_timestamp;
QString work_dir;
QString save_dir;
QString id;
bool initialized;
+ QPointer<RKMDIWindow> window;
/** map of outputs. */
- static QMap<QString, RKOutputDirectory> outputs;
+ static QMap<QString, RKOutputDirectory*> outputs;
- void backendActivateOutputDirectory (const QString& dir, RCommandChain* chain);
- QString createOutputDirectoryInternal ();
+ GenericRCallResult import(const QString& from);
+ static RKOutputDirectory* createOutputDirectoryInternal();
static bool isRKWwardOutputDirectory (const QString &dir);
static bool isOutputDirectoryModified (const QString &dir);
QString dropOutputDirectoryInternal (const QString& dir);
-
};
#endif
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.output.R b/rkward/rbackend/rpackages/rkward/R/rk.output.R
index c5e991bd..4950459e 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.output.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.output.R
@@ -75,7 +75,7 @@ if and only if there are unsaved changes pending."
},
close=function(discard=NULL) {
"Forget about this output file, also closing any open views. Note: Trying to call any further methods on this object will fail."
- .rk.do.call("output", c ("close", .checkId(), isTRUE(discard), isTRUE(ask)))
+ .rk.do.call("output", c ("close", .checkId(), if (is.Null(discard)) "ask" else isTRUE(discard)))
id=NULL
},
view=function(raise=TRUE) {
More information about the rkward-tracker
mailing list