[education/rkward/devel/workspace_output] rkward: More ground-work on output directories.

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


Git commit f99fd235b5bd5539fa5b22e71ada5a96d258e7bb by Thomas Friedrichsmeier.
Committed on 11/07/2017 at 10:36.
Pushed by tfry into branch 'devel/workspace_output'.

More ground-work on output directories.

M  +1    -1    rkward/rbackend/rkrinterface.cpp
M  +116  -3    rkward/windows/rkhtmlwindow.cpp
M  +10   -3    rkward/windows/rkhtmlwindow.h

https://invent.kde.org/education/rkward/commit/f99fd235b5bd5539fa5b22e71ada5a96d258e7bb

diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 285236f5..2e3a53d7 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 () + "/output/index.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
+		RKOutputWindowManager::self ()->createOutputDirectory (chain);
 
 		closeChain (chain);
 	} else if (command->getFlags () == GET_R_VERSION) {
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index a1cfc7a3..71d79516 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -1232,7 +1232,10 @@ QString RKOutputWindowManager::saveOutputDirectoryAs (const QString& dir, const
 
 		dest = QDir::cleanPath (dialog.selectedFiles ().value (0));
 		if (ask_overwrite && QFileInfo (dest).exists ()) {
-#warning: TODO: We really need some marker to make extra sure that we are not overwriting just _any_ directory, only ones that appear to be RKWard outputs.
+			if (!isRKWwardOutputDirectory (dest)) {
+				return i18n ("The directory %1 exists, but does not appear to be an RKWard output directory. Refusing to overwrite it.", dest);
+			}
+
 			const QString warning = i18n ("Are you sure you want to overwrite the existing directory '%1'? All current contents, including subdirectories will be lost.", dest);
 			KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel (RKWardMainWindow::getMain (), warning, i18n ("Overwrite Directory?"), KStandardGuiItem::overwrite (),
 			                                                                 KStandardGuiItem::cancel (), QString (), KMessageBox::Options (KMessageBox::Notify | KMessageBox::Dangerous));
@@ -1245,6 +1248,10 @@ QString RKOutputWindowManager::saveOutputDirectoryAs (const QString& dir, const
 	// If destination already exists, rename it before copying, so we can restore the save in case of an error
 	QString tempname;
 	if (QFileInfo (dest).exists ()) {
+		if (!isRKWwardOutputDirectory (dest)) {
+			return i18n ("The directory %1 exists, but does not appear to be an RKWard output directory. Refusing to overwrite it.", dest);
+		}
+
 		tempname = dest + '~';
 		while (QFileInfo (tempname).exists ()) {
 			tempname.append ('~');
@@ -1273,7 +1280,7 @@ QString RKOutputWindowManager::saveOutputDirectoryAs (const QString& dir, const
 	return QString ();
 }
 
-QString RKOutputWindowManager::importOutputDirectory (const QString& _dir, const QString& index_file, bool ask_revert, RCommandChain* chain) {
+QString RKOutputWindowManager::importOutputDirectory (const QString& _dir, const QString& _index_file, bool ask_revert, RCommandChain* chain) {
 	RK_TRACE (APP);
 
 	QFileInfo fi (_dir);
@@ -1294,6 +1301,27 @@ QString RKOutputWindowManager::importOutputDirectory (const QString& _dir, const
 		}
 	}
 
+	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 ();
+		}
+	}
+
+	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");
+		}
+	}
+
 	QString dest = createOutputDirectoryInternal ();
 
 	if (!copyDirRecursively (dir, dest)) {
@@ -1303,7 +1331,7 @@ QString RKOutputWindowManager::importOutputDirectory (const QString& _dir, const
 
 	OutputDirectory od;
 	od.index_file = index_file;
-	od.save_dir = dir;
+	if (was_rkward_ouput) od.save_dir = dir;
 	od.saved_hash = hashDirectoryState (dest);
 	od.save_timestamp = QDateTime::currentDateTime ();
 	outputs.insert (dest, od);
@@ -1345,9 +1373,94 @@ QString RKOutputWindowManager::createOutputDirectoryInternal () {
 		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 RKOutputWindowManager::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)) {
+			if (KMessageBox::warningContinueCancel (RKWardMainWindow::getMain (), i18n ("The output directory %1 has unsaved changes, which will be lost by dropping it. Are you sure you want to proceed?", work_dir)) != KMessageBox::Continue) return i18n ("Cancelled");
+		}
+	}
+
+	QString error = dropOutputDirectoryInternal (work_dir);
+	if (!error.isEmpty ()) return error;
+
+	if (current_default_path.startsWith (work_dir)) {
+		if (!outputs.isEmpty ()) {
+			backendActivateOutputDirectory (outputs.constBegin ().key (), chain);
+		} else {
+			createOutputDirectory (chain);
+		}
+	}
+
+	return QString ();
+}
+
+QString RKOutputWindowManager::dropOutputDirectoryInternal (const QString& dir) {
+	RK_TRACE (APP);
+
+	if (!isRKWwardOutputDirectory (dir)) {
+		RK_ASSERT (false); // this should not happen unless user messes with the temporary file, but we better play it safe.
+		return (i18n ("The directory %1 does not appear to be an RKWard output directory. Refusing to remove it.", dir));
+	}
+	outputs.remove (dir);
+	QDir (dir).removeRecursively ();
+
+	QStringList paths = windows.keys ();
+	for (int i = 0; i < paths.size (); ++i) {
+		if (paths[i].startsWith (dir)) {
+//			RKWorkplace::closeWindows (windows.values (paths[i]));  // NOTE: Won't work with current compilers
+			QList<RKHTMLWindow*> wins = windows.values (paths[i]);
+			for (int j = 0; j < wins.size (); ++j) {
+				RKWorkplace::mainWorkplace ()->closeWindow (wins[j]);
+			}
+		}
+	}
+
+	return QString ();
+}
+
+void RKOutputWindowManager::purgeAllOututputDirectories () {
+	RK_TRACE (APP);
+
+	QStringList output_dirs = outputs.keys ();
+	for (int i = output_dirs.size () - 1; i >= 0; --i) {
+		if (i > 0) dropOutputDirectoryInternal (output_dirs[i]);
+		else dropOutputDirectory (output_dirs[i], false);
+	}
+}
+
+QStringList RKOutputWindowManager::modifiedOutputDirectories() const {
+	RK_TRACE (APP);
+
+	QStringList ret;
+	for (QMap<QString, OutputDirectory>::const_iterator it = outputs.constBegin (); it != outputs.constEnd (); ++it) {
+		if (it.value ().saved_hash != hashDirectoryState (it.key ())) ret.append (it.key ());
+	}
+	return ret;
+}
+
+bool RKOutputWindowManager::isRKWwardOutputDirectory (const QString& dir) {
+	RK_TRACE (APP);
+
+	return (QDir (dir).exists (QStringLiteral ("rkward_output_marker.txt")));
+}
+
 void RKOutputWindowManager::backendActivateOutputDirectory (const QString& dir, RCommandChain* chain) {
 	RK_TRACE (APP);
 
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 9a5ff586..d7d0812a 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -227,7 +227,7 @@ public:
 /** 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 error message, if any, and empty string in case of success */
-	QString importOutputDirectory (const QString& dir, const QString& index_file, bool ask_revert = true, RCommandChain* chain = 0);
+	QString importOutputDirectory (const QString& dir, const QString& index_file=QString (), bool ask_revert = true, RCommandChain* chain = 0);
 /** Save the given output directory to the locaiton it was last saved to / imported from. If the output directory has not been saved / imported, yet, prompt the user for a destination.
     @param index_path output directory to save
     @returns error message, if any, an empty string in case of success */
@@ -240,8 +240,13 @@ public:
 /** Create a new empty output directory.
     @returns path of the new directory */
 	QString createOutputDirectory (RCommandChain* chain = 0);
-/** Drop the given output directory. If it was the active directory, activate another output file. Return the new active output file. */
-	QString dropOutputDirectory (const QString& index_path, bool ask=true, RCommandChain* chain = 0);
+/** Drop the given output directory. If it was the active directory, activate another output file. Return error message if any, an empty string in case of success. */
+	QString dropOutputDirectory (const QString& dir, bool ask=true, RCommandChain* chain = 0);
+
+/** Return a list of all current output directories that have been modified. Used for asking for save during shutdown. */
+	QStringList modifiedOutputDirectories () const;
+/** Use with case! Purges all current output directories, saved or not. You should query modifiedOutputDirectories (), and make sure to prompt for saving, before calling this. For use during shutdown. */
+	void purgeAllOututputDirectories ();
 private:
 	RKOutputWindowManager ();
 	~RKOutputWindowManager ();
@@ -261,6 +266,8 @@ private:
 	QMap<QString, OutputDirectory> outputs;
 	void backendActivateOutputDirectory (const QString& dir, RCommandChain* chain);
 	QString createOutputDirectoryInternal ();
+	bool isRKWwardOutputDirectory (const QString &dir);
+	QString dropOutputDirectoryInternal (const QString& dir);
 private slots:
 	void fileChanged (const QString &path);
 	void windowDestroyed (QObject *window);




More information about the rkward-tracker mailing list