On Mon, 27 Jul 2009 14:34:36 +0200, Jonas wrote:

>Am 27.07.2009 13:59, schrieb Enrico Tröger:
>> Sorry, I don't see how this could be an improvement.
>Well just open some new tab and set its label to "Loading foo.ext..." 
>and you're done ;-)

Hmm ok. Not sure I would like this but it could be at least a start of
a solution for the mentioned problems.


>> [...]e.g. when
>> compiling/building/running the current file. At this point we need
>> synchronous saving otherwise the whole point in saving a file prior
>> to an operation which relies on it becomes useless.
>Yes, but that's the only case in that you need synchronous saving.

There was another case as well but I can't remember it right now,
unfortunately.
Anyway, attached is the patch I mentioned. It's a few months old so it
probably won't apply cleanly anymore against trunk but it should be
easily possible to manually replay the changes.
Note that the patch is in an early alpha state, I only tested it
shortly and there might be still tons of problems.
But if anyone wants to work on this, feel free to do it. I can't spend
any time on this in the next few weeks, sorry.


Regards,
Enrico

-- 
Get my GPG key from http://www.uvena.de/pub.asc
diff --git a/configure.in b/configure.in
index f94a741..00e03a7 100644
--- a/configure.in
+++ b/configure.in
@@ -128,7 +128,7 @@ fi


 # GTK checks
-gtk_modules="gtk+-2.0 >= 2.6.0"
+gtk_modules="gtk+-2.0 >= 2.6.0 gthread-2.0"
 PKG_CHECK_MODULES(GTK, [$gtk_modules])
 AC_SUBST(GTK_CFLAGS)
 AC_SUBST(GTK_LIBS)
diff --git a/src/callbacks.c b/src/callbacks.c
index 688c910..377b86c 100644
--- a/src/callbacks.c
+++ b/src/callbacks.c
@@ -182,7 +182,7 @@ on_save1_activate                      (GtkMenuItem     *menuitem,
 		if (doc->file_name == NULL)
 			dialogs_show_save_as();
 		else
-			document_save_file(doc, FALSE);
+			document_save_file_async(doc, FALSE);
 	}
 }

@@ -214,7 +214,7 @@ on_save_all1_activate                  (GtkMenuItem     *menuitem,
 			dialogs_show_save_as();
 		}
 		else
-			document_save_file(doc, FALSE);
+			document_save_file_async(doc, FALSE);
 	}
 	treeviews_update_tag_list(cur_doc, TRUE);
 	ui_set_window_title(cur_doc);
diff --git a/src/document.c b/src/document.c
index 6711282..b314233 100644
--- a/src/document.c
+++ b/src/document.c
@@ -1430,52 +1430,125 @@ _("An error occurred while converting the file from UTF-8 in \"%s\". The file re
 }


-static gint write_data_to_disk(GeanyDocument *doc, const gchar *data, gint len)
+typedef struct
+{
+	GeanyDocument *doc;
+	gchar *text;
+	gsize len;
+	gint err;
+	gboolean async;
+} SaveData;
+
+
+/* Callback which is called from the saving thread when it is finished, to do GTK stuff */
+static gboolean save_file_finished(gpointer data)
 {
+	SaveData *sd = data;
+	gboolean async = sd->async; /* cache the async flag as we need it after we freed 'sd' */
+
+	if (sd->err != 0)
+	{
+		ui_set_statusbar(TRUE, _("Error saving file (%s)."), g_strerror(sd->err));
+		dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR,
+			_("Error saving file."), g_strerror(sd->err));
+		utils_beep();
+		sd->doc->priv->save_in_progress = FALSE;
+		return FALSE;
+	}
+
+	/* store the opened encoding for undo/redo */
+	store_saved_encoding(sd->doc);
+
+	/* ignore the following things if we are quitting */
+	if (! main_status.quitting)
+	{
+		sci_set_savepoint(sd->doc->editor->sci);
+
+		/* stat the file to get the timestamp, otherwise on Windows the actual
+		 * timestamp can be ahead of time(NULL) */
+		if (file_prefs.disk_check_timeout > 0)
+			document_update_timestamp(sd->doc);
+
+		/* update filetype-related things */
+		/** TODO possibly refactor document_set_filetype() to move separate updating the
+		 *  tm_file in the threaded code and keep the GUI updating outside */
+		document_set_filetype(sd->doc, sd->doc->file_type);
+
+		tm_workspace_update(TM_WORK_OBJECT(app->tm_workspace), TRUE, TRUE, FALSE);
+
+		document_update_tab_label(sd->doc);
+
+		msgwin_status_add(_("File %s saved."), sd->doc->file_name);
+		ui_update_statusbar(sd->doc, -1);
+#ifdef HAVE_VTE
+		vte_cwd(sd->doc->file_name, FALSE);
+#endif
+	}
+	sd->doc->priv->save_in_progress = FALSE;
+	g_signal_emit_by_name(geany_object, "document-save", sd->doc);
+
+	g_free(sd->text);
+	g_free(sd);
+
+	return (async) ? FALSE : TRUE;
+}
+
+
+/* Saving thread to write the data to the file on disk.
+ * Don't do any GTK stuff in here. save_file_finished() is called at the end as an idle function
+ * to update GUI things in the GTK main thread.
+ * If called asynchronously, it returns always NULL.
+ * If called synchronously, it returns NULL on any error or non-NULL on success. */
+static gpointer save_file_start(gpointer data)
+{
+	SaveData *sd = data;
 	FILE *fp;
-	gint bytes_written;
+	gsize bytes_written;
 	gchar *locale_filename = NULL;
-	gint err = 0;

-	g_return_val_if_fail(data != NULL, EINVAL);
+	g_return_val_if_fail(data != NULL && sd->text != NULL, NULL);

-	locale_filename = utils_get_locale_from_utf8(doc->file_name);
+	locale_filename = utils_get_locale_from_utf8(sd->doc->file_name);
 	fp = g_fopen(locale_filename, "wb");
 	if (fp == NULL)
 	{
 		g_free(locale_filename);
-		return errno;
+		sd->err = errno;
+
+		g_idle_add(save_file_finished, sd);
+		return NULL; /* this is equal to g_thread_exit() */
 	}
+	g_free(locale_filename);

-	bytes_written = fwrite(data, sizeof(gchar), len, fp);
+	bytes_written = fwrite(sd->text, sizeof(gchar), sd->len, fp);

-	if (len != bytes_written)
-		err = errno;
+	if (sd->len != bytes_written)
+		sd->err = errno;

 	fclose(fp);

-	return err;
+	/* now the file is on disk, set real_path */
+	if (sd->doc->real_path == NULL)
+	{
+		sd->doc->real_path = get_real_path_from_utf8(sd->doc->file_name);
+	}
+
+	if (sd->async)
+	{
+		/* delegate further processing and handling error messages
+		 * to GTK's main thread through the main loop */
+		g_idle_add(save_file_finished, sd);
+		return NULL; /* this is equal to g_thread_exit() */
+	}
+	else
+		return (save_file_finished(sd)) ? GINT_TO_POINTER(TRUE) : NULL;
 }


-/**
- *  Save the @a document. Saving includes replacing tabs by spaces,
- *  stripping trailing spaces and adding a final new line at the end of the file (all only if
- *  user enabled these features). The filetype is set again or auto-detected if it wasn't
- *  set yet. After all, the "document-save" signal is emitted for plugins.
- *
- *  If the file is not modified, this functions does nothing unless force is set to @c TRUE.
- *
- *  @param doc The %document to save.
- *  @param force Whether to save the file even if it is not modified (e.g. for Save As).
- *
- *  @return @c TRUE if the file was saved or @c FALSE if the file could not or should not be saved.
- **/
-gboolean document_save_file(GeanyDocument *doc, gboolean force)
+static gboolean save_file_init(GeanyDocument *doc, SaveData *sd, gboolean force)
 {
 	gchar *data;
 	gsize len;
-	gint err;

 	if (doc == NULL)
 		return FALSE;
@@ -1534,54 +1607,72 @@ gboolean document_save_file(GeanyDocument *doc, gboolean force)
 		len = strlen(data);
 	}

-	/* actually write the content of data to the file on disk */
-	err = write_data_to_disk(doc, data, len);
-	g_free(data);
+	sd->doc = doc;
+	sd->text = data;
+	sd->len = len;

-	if (err != 0)
-	{
-		ui_set_statusbar(TRUE, _("Error saving file (%s)."), g_strerror(err));
-		dialogs_show_msgbox_with_secondary(GTK_MESSAGE_ERROR,
-			_("Error saving file."), g_strerror(err));
-		utils_beep();
-		return FALSE;
-	}
+	return TRUE;
+}

-	/* now the file is on disk, set real_path */
-	if (doc->real_path == NULL)
-	{
-		doc->real_path = get_real_path_from_utf8(doc->file_name);
-	}

-	/* store the opened encoding for undo/redo */
-	store_saved_encoding(doc);
+/**
+ *  Save the @a document. Saving includes replacing tabs by spaces,
+ *  stripping trailing spaces and adding a final new line at the end of the file (all only if
+ *  user enabled these features). The filetype is set again or auto-detected if it wasn't
+ *  set yet. After all, the "document-save" signal is emitted for plugins on success.
+ *
+ *  If the file is not modified, this functions does nothing unless force is set to @c TRUE.
+ *
+ *  @param doc The %document to save.
+ *  @param force Whether to save the file even if it is not modified (e.g. for Save As).
+ *
+ *  @return @c TRUE if the file was saved or @c FALSE if the file could not or should not be saved.
+ **/
+gboolean document_save_file(GeanyDocument *doc, gboolean force)
+{
+	SaveData *save_data = g_new0(SaveData, 1);

-	/* ignore the following things if we are quitting */
-	if (! main_status.quitting)
+	if (doc->priv->save_in_progress || ! save_file_init(doc, save_data, force))
 	{
-		sci_set_savepoint(doc->editor->sci);
+		g_free(save_data);
+		return FALSE;
+	}

-		/* stat the file to get the timestamp, otherwise on Windows the actual
-		 * timestamp can be ahead of time(NULL) */
-		if (file_prefs.disk_check_timeout > 0)
-			document_update_timestamp(doc);
+	save_data->async = FALSE;
+	return (save_file_start(save_data) != NULL);
+}

-		/* update filetype-related things */
-		document_set_filetype(doc, doc->file_type);

-		tm_workspace_update(TM_WORK_OBJECT(app->tm_workspace), TRUE, TRUE, FALSE);
+/**
+ *  Asynchronous variant of document_save_file().
+ *  @a Document is saved to disk in a separate thread so the GUI keeps responsive even if the
+ *  saving operation takes some time (e.g. on slow remote connections).
+ *
+ *  Note that there is no direct way to get the result of the operation. But you can connect to
+ *  "document-save" signal which is emitted once the whole save operation has been finished
+ *  successfully.
+ *
+ *  @param doc The %document to save.
+ *  @param force Whether to save the file even if it is not modified (e.g. for Save As).
+ **/
+/* TODO if this works reliable, add this function to the plugin API */
+void document_save_file_async(GeanyDocument *doc, gboolean force)
+{
+	SaveData *save_data;

-		document_update_tab_label(doc);
+	/* TODO start threads only for non-local files, use normal saving for normal files */

-		msgwin_status_add(_("File %s saved."), doc->file_name);
-		ui_update_statusbar(doc, -1);
-#ifdef HAVE_VTE
-		vte_cwd(doc->file_name, FALSE);
-#endif
+	save_data = g_new0(SaveData, 1);
+	if (doc->priv->save_in_progress || ! save_file_init(doc, save_data, force))
+	{
+		g_free(save_data);
+		return;
 	}
-	g_signal_emit_by_name(geany_object, "document-save", doc);

-	return TRUE;
+	save_data->async = TRUE;
+	doc->priv->save_in_progress = TRUE;
+	/* start a thread to write the file to disk */
+	g_thread_create(save_file_start, save_data, FALSE, NULL);
 }


diff --git a/src/document.h b/src/document.h
index a2f41f1..c15d561 100644
--- a/src/document.h
+++ b/src/document.h
@@ -146,6 +146,8 @@ GeanyDocument* document_find_by_real_path(const gchar *realname);

 gboolean document_save_file(GeanyDocument *doc, gboolean force);

+void document_save_file_async(GeanyDocument *doc, gboolean force);
+
 gboolean document_save_file_as(GeanyDocument *doc, const gchar *utf8_fname);

 GeanyDocument* document_open_file(const gchar *locale_filename, gboolean readonly,
diff --git a/src/documentprivate.h b/src/documentprivate.h
index 2fbde00..0407a42 100644
--- a/src/documentprivate.h
+++ b/src/documentprivate.h
@@ -67,6 +67,7 @@ typedef struct GeanyDocumentPrivate
 	gboolean		colourise_needed;	/* use document.c:queue_colourise() instead */
 	gint			line_count;			/* Number of lines in the document. */
 	gint			symbol_list_sort_mode;
+	gboolean		save_in_progress;
 }
 GeanyDocumentPrivate;

diff --git a/src/main.c b/src/main.c
index 7fe7cc3..c6f6d98 100644
--- a/src/main.c
+++ b/src/main.c
@@ -873,6 +873,9 @@ gint main(gint argc, gchar **argv)

 	log_handlers_init();

+	if (! g_thread_supported())
+		g_thread_init(NULL);
+
 	app = g_new0(GeanyApp, 1);
 	memset(&main_status, 0, sizeof(GeanyStatus));
 	memset(&prefs, 0, sizeof(GeanyPrefs));
diff --git a/src/treeviews.c b/src/treeviews.c
index 836c1fe..7bd412c 100644
--- a/src/treeviews.c
+++ b/src/treeviews.c
@@ -484,7 +484,7 @@ static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_da
 				}
 				case OPENFILES_ACTION_SAVE:
 				{
-					document_save_file(doc, FALSE);
+					document_save_file_async(doc, FALSE);
 					break;
 				}
 				case OPENFILES_ACTION_RELOAD:
diff --git a/wscript b/wscript
index e5c9506..0ecb772 100644
--- a/wscript
+++ b/wscript
@@ -175,6 +175,8 @@ def configure(conf):

 	conf.check_cfg(package='gtk+-2.0', atleast_version='2.6.0', uselib_store='GTK',
 		mandatory=True, args='--cflags --libs')
+	conf.check_cfg(package='gthread-2.0', atleast_version='2.6.0', uselib_store='GTHREAD',
+		mandatory=True, args='--cflags --libs')

 	# GTK version check
 	have_gtk_210 = False
@@ -301,7 +303,7 @@ def build(bld):
 	obj.target			= 'geany'
 	obj.source			= geany_sources
 	obj.includes		= '. src/ scintilla/include/ tagmanager/include/'
-	obj.uselib			= 'GTK'
+	obj.uselib			= 'GTK GTHREAD'
 	obj.uselib_local	= 'scintilla tagmanager'

 	# geanyfunctions.h

Attachment: pgppAbQwMSyxA.pgp
Description: PGP signature

_______________________________________________
Geany-devel mailing list
Geany-devel@uvena.de
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel

Reply via email to