aboutsummaryrefslogtreecommitdiffstats
path: root/composer/e-composer-autosave.c
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2010-09-03 01:21:08 +0800
committerMatthew Barnes <mbarnes@redhat.com>2010-09-03 01:37:31 +0800
commit429234ff213ba04b6d0b02a28ed68aaa8af7c02c (patch)
tree69fd485d98a78148137ab29e5c1e8d16add7c0f1 /composer/e-composer-autosave.c
parentcccdb143a571cde36db9fe906864647aab546cff (diff)
downloadgsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.gz
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.zst
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.zip
Convert composer autosave to an EExtension.
Given the way the autosave feature was awkwardly bolted on to the composer, an EExtension seemed like a natural fit. And it helped clean up some object lifecycle hacks (and bugs). What we have now is a new module consisting of two EExtensions: EComposerAutosave extends EMsgComposer and determines when to kick off an asynchronous autosave operation. EComposerRegistry extends EShell and offers to restore orphaned autosave files on startup (which is also asynchronous now). e-autosave-utils.c holds the actual asynchronous functions and a few other miscellaneous utility functions. Source code for the new module lives in /modules/composer-autosave.
Diffstat (limited to 'composer/e-composer-autosave.c')
-rw-r--r--composer/e-composer-autosave.c511
1 files changed, 0 insertions, 511 deletions
diff --git a/composer/e-composer-autosave.c b/composer/e-composer-autosave.c
deleted file mode 100644
index 82a44d570a..0000000000
--- a/composer/e-composer-autosave.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with the program; if not, see <http://www.gnu.org/licenses/>
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- */
-
-#include "e-composer-autosave.h"
-
-#include <errno.h>
-#include <sys/stat.h>
-#include <glib/gi18n.h>
-#include <glib/gstdio.h>
-
-#include <e-util/e-alert-dialog.h>
-#include <e-util/e-util.h>
-
-#define AUTOSAVE_PREFIX ".evolution-composer.autosave"
-#define AUTOSAVE_SEED AUTOSAVE_PREFIX "-XXXXXX"
-#define AUTOSAVE_INTERVAL 60 /* seconds */
-
-typedef struct _AutosaveState AutosaveState;
-
-struct _AutosaveState {
- EMsgComposer *composer;
- GFile *file;
- gboolean changed;
- guint source_id; /* timeout source ID */
- gboolean enabled;
- gboolean error_shown;
-};
-
-static GList *autosave_registry;
-
-static void composer_changed_cb (EMsgComposer *composer);
-
-static EMsgComposer *
-composer_autosave_registry_lookup (const gchar *basename)
-{
- GList *iter;
-
- /* Find the composer with the given autosave filename. */
- for (iter = autosave_registry; iter != NULL; iter = iter->next) {
- EMsgComposer *composer = iter->data;
- AutosaveState *state;
- gchar *_basename;
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- if (state == NULL || state->file == NULL)
- continue;
-
- _basename = g_file_get_basename (state->file);
- if (strcmp (_basename, basename) == 0) {
- g_free (_basename);
- return composer;
- }
- g_free (_basename);
- }
-
- return NULL;
-}
-
-static AutosaveState *
-composer_autosave_state_new (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- state = g_slice_new0 (AutosaveState);
- state->enabled = TRUE;
- state->changed = FALSE;
- state->source_id = 0;
- state->composer = composer;
-
- g_signal_connect (
- composer, "notify::changed",
- G_CALLBACK (composer_changed_cb), NULL);
-
- return state;
-}
-
-static void
-composer_autosave_state_free (AutosaveState *state)
-{
- if (state->source_id)
- g_source_remove (state->source_id);
- if (state->file)
- g_object_unref (state->file);
- g_slice_free (AutosaveState, state);
-}
-
-static gboolean
-composer_autosave_state_open (AutosaveState *state,
- GError **error)
-{
- const gchar *user_data_dir;
- gchar *path;
- gint fd;
-
- if (state->file != NULL)
- return TRUE;
-
- user_data_dir = e_get_user_data_dir ();
- path = g_build_filename (user_data_dir, AUTOSAVE_SEED, NULL);
-
- /* Since GIO doesn't have support for creating temporary files
- * from a template (and in a given directory), we have to use
- * g_mkstemp(), which brings a small risk of overwriting another
- * autosave file. The risk is, however, miniscule. */
- errno = 0;
- fd = g_mkstemp (path);
- if (fd == -1) {
- g_set_error (
- error, G_FILE_ERROR,
- g_file_error_from_errno (errno),
- "%s", g_strerror (errno));
- g_free (path);
- return FALSE;
- }
-
- close (fd);
-
- /* Create the GFile */
- state->file = g_file_new_for_path (path);
- g_free (path);
-
- return TRUE;
-}
-
-static void
-composer_autosave_finish_cb (EMsgComposer *composer,
- GAsyncResult *result)
-{
- AutosaveState *state;
- GError *error = NULL;
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- e_composer_autosave_snapshot_finish (composer, result, &error);
-
- if (error != NULL) {
- gchar *basename;
-
- if (G_IS_FILE (state->file))
- basename = g_file_get_basename (state->file);
- else
- basename = g_strdup (" ");
-
- /* Only show one error dialog at a
- * time to avoid cascading dialogs. */
- if (!state->error_shown) {
- state->error_shown = TRUE;
- e_alert_run_dialog_for_args (
- GTK_WINDOW (composer),
- "mail-composer:no-autosave",
- basename, error->message, NULL);
- state->error_shown = FALSE;
- } else
- g_warning ("%s: %s", basename, error->message);
-
- g_free (basename);
- g_error_free (error);
- }
-}
-
-static gboolean
-composer_autosave_timeout (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (composer != NULL, FALSE);
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, FALSE);
- g_return_val_if_fail (state->composer == composer, FALSE);
-
- if (!state->changed) {
- state->source_id = 0;
- return FALSE;
- }
-
- composer = state->composer;
-
- if (e_composer_autosave_get_enabled (composer)) {
- state->changed = FALSE;
- e_composer_autosave_snapshot_async (
- composer, (GAsyncReadyCallback)
- composer_autosave_finish_cb, NULL);
- }
-
- return TRUE;
-}
-
-static void
-composer_changed_cb (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_if_fail (composer != NULL);
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
- g_return_if_fail (state->composer == composer);
-
- g_object_get (G_OBJECT (composer), "changed", &state->changed, NULL);
-
- if (state->changed && state->source_id == 0) {
- state->source_id = g_timeout_add_seconds (
- AUTOSAVE_INTERVAL, (GSourceFunc)
- composer_autosave_timeout, state->composer);
- }
-}
-
-static void
-composer_autosave_notify (gpointer unused,
- GObject *where_the_object_was)
-{
- /* Remove the dead composer from the registry. */
- autosave_registry = g_list_remove (
- autosave_registry, where_the_object_was);
-}
-
-GList *
-e_composer_autosave_find_orphans (GError **error)
-{
- GDir *dir;
- const gchar *dirname;
- const gchar *basename;
- GList *orphans = NULL;
-
- dirname = e_get_user_data_dir ();
- dir = g_dir_open (dirname, 0, error);
- if (dir == NULL)
- return NULL;
-
- /* Scan the user directory for autosave files. */
- while ((basename = g_dir_read_name (dir)) != NULL) {
- const gchar *errmsg;
- gchar *filename;
- struct stat st;
-
- /* Is this an autosave file? */
- if (!g_str_has_prefix (basename, AUTOSAVE_PREFIX))
- continue;
-
- /* Is this an orphaned autosave file? */
- if (composer_autosave_registry_lookup (basename) != NULL)
- continue;
-
- filename = g_build_filename (dirname, basename, NULL);
-
- /* Try to examine the autosave file. Failure here
- * is non-fatal; just emit a warning and move on. */
- errno = 0;
- if (g_stat (filename, &st) < 0) {
- errmsg = g_strerror (errno);
- g_warning ("%s: %s", filename, errmsg);
- g_free (filename);
- continue;
- }
-
- /* If the file is empty, delete it. Failure here
- * is non-fatal; just emit a warning and move on. */
- if (st.st_size == 0) {
- errno = 0;
- if (g_unlink (filename) < 0) {
- errmsg = g_strerror (errno);
- g_warning ("%s: %s", filename, errmsg);
- }
- g_free (filename);
- continue;
- }
-
- orphans = g_list_prepend (orphans, filename);
- }
-
- g_dir_close (dir);
-
- return g_list_reverse (orphans);
-}
-
-void
-e_composer_autosave_register (EMsgComposer *composer)
-{
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- g_object_set_data_full (
- G_OBJECT (composer), "autosave",
- composer_autosave_state_new (composer),
- (GDestroyNotify) composer_autosave_state_free);
-
- autosave_registry = g_list_prepend (autosave_registry, composer);
-
- g_object_weak_ref (
- G_OBJECT (composer), (GWeakNotify)
- composer_autosave_notify, NULL);
-}
-
-void
-e_composer_autosave_unregister (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- if (state == NULL || state->file == NULL)
- return;
-
- g_file_delete (state->file, NULL, NULL);
-
- g_object_set_data (G_OBJECT (composer), "autosave", NULL);
-}
-
-static void
-autosave_snapshot_splice_cb (GOutputStream *output_stream,
- GAsyncResult *result,
- GSimpleAsyncResult *simple)
-{
- GError *error = NULL;
-
- g_output_stream_splice_finish (output_stream, result, &error);
-
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_error_free (error);
- }
-
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
-}
-
-static void
-autosave_snapshot_cb (GFile *file,
- GAsyncResult *result,
- GSimpleAsyncResult *simple)
-{
- GObject *object;
- EMsgComposer *composer;
- CamelMimeMessage *message;
- GFileOutputStream *output_stream;
- GInputStream *input_stream;
- CamelStream *camel_stream;
- GByteArray *buffer;
- GError *error = NULL;
-
- object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
-
- output_stream = g_file_replace_finish (file, result, &error);
-
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- g_error_free (error);
- return;
- }
-
- /* Extract a MIME message from the composer. */
- composer = E_MSG_COMPOSER (object);
- message = e_msg_composer_get_message_draft (composer, &error);
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (output_stream);
- g_object_unref (simple);
- return;
- }
-
- /* Decode the MIME part to an in-memory buffer. We have to do
- * this because CamelStream is synchronous-only, and using threads
- * is dangerous because CamelDataWrapper is not reentrant. */
- buffer = g_byte_array_new ();
- camel_stream = camel_stream_mem_new ();
- camel_stream_mem_set_byte_array (
- CAMEL_STREAM_MEM (camel_stream), buffer);
- camel_data_wrapper_decode_to_stream (
- CAMEL_DATA_WRAPPER (message), camel_stream, NULL);
- g_object_unref (message);
- g_object_unref (camel_stream);
-
- /* Load the buffer into a GMemoryInputStream.
- * But watch out for zero length MIME parts. */
- input_stream = g_memory_input_stream_new ();
- if (buffer->len > 0)
- g_memory_input_stream_add_data (
- G_MEMORY_INPUT_STREAM (input_stream),
- buffer->data, (gssize) buffer->len,
- (GDestroyNotify) g_free);
- g_byte_array_free (buffer, FALSE);
-
- /* Splice the input and output streams */
- g_output_stream_splice_async (
- G_OUTPUT_STREAM (output_stream), input_stream,
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
- G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
- G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)
- autosave_snapshot_splice_cb, simple);
-
- g_object_unref (output_stream);
- g_object_unref (input_stream);
-}
-
-void
-e_composer_autosave_snapshot_async (EMsgComposer *composer,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- AutosaveState *state;
- GSimpleAsyncResult *simple;
- GError *error = NULL;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- simple = g_simple_async_result_new (
- G_OBJECT (composer), callback, user_data,
- e_composer_autosave_snapshot_async);
-
- /* If the contents are unchanged, exit early. */
- if (!gtkhtml_editor_get_changed (GTKHTML_EDITOR (composer))) {
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- return;
- }
-
- /* Open the autosave file on-demand. */
- if (!composer_autosave_state_open (state, &error)) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- return;
- }
-
- /* Overwrite the file. */
- g_file_replace_async (
- state->file, NULL, FALSE, G_FILE_CREATE_PRIVATE,
- G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)
- autosave_snapshot_cb, simple);
-}
-
-gboolean
-e_composer_autosave_snapshot_finish (EMsgComposer *composer,
- GAsyncResult *result,
- GError **error)
-{
- GSimpleAsyncResult *simple;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
- g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
- simple = G_SIMPLE_ASYNC_RESULT (result);
-
- /* Success is assumed in the absense of a GError. */
- return !g_simple_async_result_propagate_error (simple, error);
-}
-
-gchar *
-e_composer_autosave_get_filename (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, NULL);
-
- return g_file_get_path (state->file);
-}
-
-gboolean
-e_composer_autosave_get_enabled (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, FALSE);
-
- return state->enabled;
-}
-
-void
-e_composer_autosave_set_enabled (EMsgComposer *composer,
- gboolean enabled)
-{
- AutosaveState *state;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- state->enabled = enabled;
-}