diff options
Diffstat (limited to 'composer/e-composer-autosave.c')
-rw-r--r-- | composer/e-composer-autosave.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/composer/e-composer-autosave.c b/composer/e-composer-autosave.c new file mode 100644 index 0000000000..eda3a033e2 --- /dev/null +++ b/composer/e-composer-autosave.c @@ -0,0 +1,422 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "e-composer-autosave.h" + +#include <errno.h> +#include <sys/stat.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include <e-util/e-error.h> +#include <e-util/e-util.h> +#include <camel/camel-stream-fs.h> + +#define AUTOSAVE_PREFIX ".evolution-composer.autosave" +#define AUTOSAVE_SEED AUTOSAVE_PREFIX "-XXXXXX" +#define AUTOSAVE_INTERVAL 60000 /* 60 seconds */ + +typedef struct _AutosaveState AutosaveState; + +struct _AutosaveState { + gchar *filename; + gboolean enabled; + gboolean saved; + gint fd; +}; + +static GList *autosave_registry; +static guint autosave_source_id; + +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; + + state = g_object_get_data (G_OBJECT (composer), "autosave"); + if (state == NULL || state->filename == NULL) + continue; + + if (g_str_has_suffix (state->filename, basename)) + return composer; + } + + return NULL; +} + +static AutosaveState * +composer_autosave_state_new (void) +{ + AutosaveState *state; + + state = g_slice_new (AutosaveState); + state->filename = NULL; + state->enabled = TRUE; + state->fd = -1; + + return state; +} + +static void +composer_autosave_state_free (AutosaveState *state) +{ + if (state->fd >= 0) + close (state->fd); + + g_free (state->filename); + g_slice_free (AutosaveState, state); +} + +static gboolean +composer_autosave_state_open (AutosaveState *state, + GError **error) +{ + if (state->filename != NULL) + return TRUE; + + state->filename = g_build_filename ( + e_get_user_data_dir (), AUTOSAVE_SEED, NULL); + + errno = 0; + if ((state->fd = g_mkstemp (state->filename)) >= 0) + return TRUE; + + g_set_error ( + error, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s: %s", state->filename, g_strerror (errno)); + + g_free (state->filename); + state->filename = NULL; + + return FALSE; +} + +static void +composer_autosave_foreach (EMsgComposer *composer) +{ + /* Make sure the composer is still alive. */ + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + + if (e_composer_autosave_get_enabled (composer)) + e_composer_autosave_snapshot (composer); +} + +static gint +composer_autosave_timeout (void) +{ + g_list_foreach ( + autosave_registry, (GFunc) + composer_autosave_foreach, NULL); + + return TRUE; +} + +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); + + /* Cancel timeouts if the registry is now empty. */ + if (autosave_registry == NULL && autosave_source_id != 0) { + g_source_remove (autosave_source_id); + autosave_source_id = 0; + } +} + +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 (), + (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); + + if (autosave_source_id == 0) + autosave_source_id = g_timeout_add ( + AUTOSAVE_INTERVAL, (GSourceFunc) + composer_autosave_timeout, 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->filename == NULL) + return; + + if (e_composer_autosave_snapshot (composer)) { + close (state->fd); + g_unlink (state->filename); + } else + close (state->fd); + + g_object_set_data (G_OBJECT (composer), "autosave", NULL); +} + +gboolean +e_composer_autosave_snapshot (EMsgComposer *composer) +{ + GtkhtmlEditor *editor; + CamelMimeMessage *message; + AutosaveState *state; + CamelStream *stream; + gint camelfd; + const gchar *errmsg; + + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); + + editor = GTKHTML_EDITOR (composer); + + /* If the contents are unchanged, exit early. */ + if (!gtkhtml_editor_get_changed (editor)) + return TRUE; + + state = g_object_get_data (G_OBJECT (composer), "autosave"); + g_return_val_if_fail (state != NULL, FALSE); + + /* Open the autosave file on-demand. */ + if (!composer_autosave_state_open (state, NULL)) { + errmsg = _("Could not open autosave file"); + goto fail; + } + + /* Extract a MIME message from the composer. */ + message = e_msg_composer_get_message_draft (composer); + if (message == NULL) { + errmsg = _("Unable to retrieve message from editor"); + goto fail; + } + + /* Move to the beginning of the autosave file. */ + if (lseek (state->fd, (off_t) 0, SEEK_SET) < 0) { + camel_object_unref (message); + errmsg = g_strerror (errno); + goto fail; + } + + /* Destroy the contents of the autosave file. */ + if (ftruncate (state->fd, (off_t) 0) < 0) { + camel_object_unref (message); + errmsg = g_strerror (errno); + goto fail; + } + + /* Duplicate the file descriptor for Camel. */ + if ((camelfd = dup (state->fd)) < 0) { + camel_object_unref (message); + errmsg = g_strerror (errno); + goto fail; + } + + /* Open a CamelStream to the autosave file. */ + stream = camel_stream_fs_new_with_fd (camelfd); + + /* Write the message to the CamelStream. */ + if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream) < 0) { + camel_object_unref (message); + camel_object_unref (stream); + errmsg = g_strerror (errno); + goto fail; + } + + /* Close the CamelStream. */ + if (camel_stream_close (CAMEL_STREAM (stream)) < 0) { + camel_object_unref (message); + camel_object_unref (stream); + errmsg = g_strerror (errno); + goto fail; + } + + /* Snapshot was successful; set various flags. */ + gtkhtml_editor_set_changed (editor, FALSE); + e_composer_autosave_set_saved (composer, TRUE); + + camel_object_unref (message); + camel_object_unref (stream); + + return TRUE; + +fail: + e_error_run ( + GTK_WINDOW (composer), "mail-composer:no-autosave", + (state->filename != NULL) ? state->filename : "", + errmsg, NULL); + + return FALSE; +} + +gint +e_composer_autosave_get_fd (EMsgComposer *composer) +{ + AutosaveState *state; + + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), -1); + + state = g_object_get_data (G_OBJECT (composer), "autosave"); + g_return_val_if_fail (state != NULL, -1); + + return state->fd; +} + +const 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 state->filename; +} + +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; +} + +gboolean +e_composer_autosave_get_saved (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->saved; +} + +void +e_composer_autosave_set_saved (EMsgComposer *composer, + gboolean saved) +{ + 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->saved = saved; +} |