From 2026a45165c5c88b5f1c7208f282801b28e81c44 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Tue, 2 Mar 2010 23:35:14 -0500 Subject: Save state key file asynchronously. Avoids blocking the UI in case the save triggers an fsync() which, on ext3, synchronizes the entire filesystem. The logic is a bit tricky because we want to limit the frequency of saves and only run one save operation at a time, but we also want to do an immediate last-ditch save before finalizing the shell view. --- shell/e-shell-view.c | 111 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/shell/e-shell-view.c b/shell/e-shell-view.c index 83be139327..e955518641 100644 --- a/shell/e-shell-view.c +++ b/shell/e-shell-view.c @@ -25,6 +25,7 @@ #include #include "e-util/e-util.h" +#include "e-util/e-file-utils.h" #include "e-util/e-plugin-ui.h" #include "filter/e-rule-context.h" @@ -41,7 +42,8 @@ struct _EShellViewPrivate { gpointer shell_window; /* weak pointer */ GKeyFile *state_key_file; - guint state_save_source_id; + gpointer state_save_activity; /* weak pointer */ + guint state_save_timeout_id; gchar *title; gchar *view_id; @@ -215,39 +217,98 @@ exit: g_free (filename); } +typedef struct { + EShellView *shell_view; + gchar *contents; +} SaveStateData; + static void +shell_view_save_state_done_cb (GFile *file, + GAsyncResult *result, + SaveStateData *data) +{ + GError *error = NULL; + + e_file_replace_contents_finish (file, result, NULL, &error); + + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (data->shell_view); + g_free (data->contents); + g_slice_free (SaveStateData, data); +} + +static EActivity * shell_view_save_state (EShellView *shell_view) { EShellBackend *shell_backend; + SaveStateData *data; + EActivity *activity; GKeyFile *key_file; + GFile *file; const gchar *config_dir; gchar *contents; - gchar *filename; - GError *error = NULL; + gchar *path; shell_backend = e_shell_view_get_shell_backend (shell_view); config_dir = e_shell_backend_get_config_dir (shell_backend); - filename = g_build_filename (config_dir, "state", NULL); - - /* XXX Should do this asynchronously. */ key_file = shell_view->priv->state_key_file; + contents = g_key_file_to_data (key_file, NULL, NULL); - g_file_set_contents (filename, contents, -1, &error); - g_free (contents); + g_return_val_if_fail (contents != NULL, NULL); - if (error != NULL) { - g_warning ("%s", error->message); - g_error_free (error); - } + path = g_build_filename (config_dir, "state", NULL); + file = g_file_new_for_path (path); + g_free (path); - g_free (filename); + /* GIO does not copy the contents string, so we need to keep + * it in memory until saving is complete. We reference the + * shell view to keep it from being finalized while saving. */ + data = g_slice_new (SaveStateData); + data->shell_view = g_object_ref (shell_view); + data->contents = contents; + + /* The returned activity is a borrowed reference. */ + activity = e_file_replace_contents_async ( + file, contents, strlen (contents), NULL, + FALSE, G_FILE_CREATE_PRIVATE, (GAsyncReadyCallback) + shell_view_save_state_done_cb, data); + +#if 0 /* FIXME Enable this for 2.31 */ + e_activity_set_primary_text ( + activity, _("Saving user interface state")); +#endif + + e_shell_backend_add_activity (shell_backend, activity); + + g_object_unref (file); + + return activity; } static gboolean shell_view_state_timeout_cb (EShellView *shell_view) { - shell_view_save_state (shell_view); - shell_view->priv->state_save_source_id = 0; + EActivity *activity; + + /* If a save is still in progress, check back later. */ + if (shell_view->priv->state_save_activity != NULL) + return TRUE; + + activity = shell_view_save_state (shell_view); + + /* Set up a weak pointer that gets set to NULL when the + * activity finishes. This will tell us if we're still + * busy saving state data to disk on the next timeout. */ + shell_view->priv->state_save_activity = activity; + g_object_add_weak_pointer ( + G_OBJECT (shell_view->priv->state_save_activity), + &shell_view->priv->state_save_activity); + + shell_view->priv->state_save_timeout_id = 0; return FALSE; } @@ -421,10 +482,18 @@ shell_view_dispose (GObject *object) priv = E_SHELL_VIEW_GET_PRIVATE (object); /* Expedite any pending state saves. */ - if (priv->state_save_source_id > 0) { - g_source_remove (priv->state_save_source_id); - priv->state_save_source_id = 0; - shell_view_save_state (E_SHELL_VIEW (object)); + if (priv->state_save_timeout_id > 0) { + g_source_remove (priv->state_save_timeout_id); + priv->state_save_timeout_id = 0; + if (priv->state_save_activity == NULL) + shell_view_save_state (E_SHELL_VIEW (object)); + } + + if (priv->state_save_activity != NULL) { + g_object_remove_weak_pointer ( + G_OBJECT (priv->state_save_activity), + &priv->state_save_activity); + priv->state_save_activity = NULL; } if (priv->shell_window != NULL) { @@ -1331,14 +1400,14 @@ e_shell_view_set_state_dirty (EShellView *shell_view) g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); /* If a timeout is already scheduled, do nothing. */ - if (shell_view->priv->state_save_source_id > 0) + if (shell_view->priv->state_save_timeout_id > 0) return; source_id = g_timeout_add_seconds ( STATE_SAVE_TIMEOUT_SECONDS, (GSourceFunc) shell_view_state_timeout_cb, shell_view); - shell_view->priv->state_save_source_id = source_id; + shell_view->priv->state_save_timeout_id = source_id; } /** -- cgit v1.2.3