aboutsummaryrefslogtreecommitdiffstats
path: root/composer/e-composer-autosave.c
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2008-04-03 02:37:22 +0800
committerMatthew Barnes <mbarnes@src.gnome.org>2008-04-03 02:37:22 +0800
commit16068d9b4191ea142a9e75a50eb8d260ed2bb406 (patch)
tree835a7909cd8b352d8c414986f1f5e27697b4de98 /composer/e-composer-autosave.c
parentee50e5d68e4f1a793541f1ee4979818ed4940173 (diff)
downloadgsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.gz
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.bz2
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.lz
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.xz
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.zst
gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.zip
** Merge the mbarnes-composer branch
2008-04-02 Matthew Barnes <mbarnes@redhat.com> ** Merge the mbarnes-composer branch * configure.in: Bump libgtkhtml requirement to 3.19.1. Add gtkhtml-editor dependency for addressbook, calendar and mail. Remove print-message plugin; new composer implements this natively. * tools/Makefile.am: Remove CORBA rules for the old composer. * addressbook/gui/widgets/Makefile.am: Remove CORBA rules for the old composer. * addressbook/gui/widgets/eab-gui-util.c (eab_send_to_contact_and_email_num_list), (eab_send_contact_list_as_attachment): Adapt to new Bonobo-less composer widget. * calendar/gui/Makefile.am: Remove CORBA rules for the old composer. * calendar/gui/itip-utils.c (comp_from), (comp_to_list), (comp_subject), (comp_content_type), (comp_filename), (comp_description), (append_cal_attachments), (itip_send_comp), (reply_to_calendar_comp): Adapt to new Bonobo-less composer widget. * composer/Makefile.am: Remove CORBA rules for the old composer. * composer/e-msg-composer.c: * composer/e-msg-composer.h: EMsgComposer is now a subclass of GtkhtmlEditor. Extensive refactoring and cleanup, too much to list in detail. * composer/e-composer-header.c: * composer/e-composer-header.h: Add "sensitive" property along with get/set functions. * composer/e-composer-from-header.c: * composer/e-composer-from-header.h: Propagate "refreshed" signal from EAccountComboBox. Add function e_composer_from_header_get_account_list(). * composer/e-composer-private.c: * composer/e-composer-private.h: New files manage composer's private data. Allows other composer files to manipulate private data. * composer/e-msg-composer-hdrs.c: * composer/e-msg-composer-hdrs.h: Remove these files; replaced by EComposerHeaderTable widget. * composer/evolution-composer.c: * composer/evolution-composer.h: Remove these files; composer is now a subclass of GtkhtmlEditor. * composer/e-msg-composer-select-file.c: * composer/e-msg-composer-select-file.h: Remove these files; logic moved to e-msg-composer.c. * composer/listener.c: * composer/listener.h: Remove these files; event handlers moved to e-msg-composer.c. * composer/Composer.idl: * composer/Evolution-Composer.idl: Remove these files; composer is no longer a Bonobo object. * mail/em-composer-prefs (sig_edit_cb), (em_composer_prefs_new_signature): Adapt to new Bonobo-less signature editor. * mail/mail-signature-editor.c: * mail/mail-signature-editor.h: Rewrite the signature editor as a subclass of GtkhtmlEditor. Eliminates Bonobo from the equation. * mail/em-composer-utils.c (composer_get_message), (em_utils_composer_send_cb), (save_draft_done), (em_utils_composer_save_draft_cb), (create_new_composer), (em_utils_compose_new_message), (em_utils_compose_new_message_with_mailto), (em_utils_post_to_folder), (em_utils_post_to_url), (edit_message), (forward_attached), (forward_non_attached), (reply_get_composer), (composer_set_body), (em_utils_reply_to_message), (post_reply_to_message): Adapt to new Bonobo-less composer. * mail/mail-component-factory.c: Composer is no longer needs a Bonobo factory. * mail/mail-config.c: Fix style pattern for EMsgComposer widgets. * plugins/groupwise/mail-send-options.c (org_gnome_composer_send_options): Adapt to streamlined EMsgComposer API. * plugins/exchange-operations/Makefile.am: Add EVOLUTION_MAIL_CFLAGS and EVOLUTION_MAIL_LIBS. * plugins/exchange-operations/exchange-mail-send-options.c (append_to_header), (org_gnome_exchange_send_options): Adapt to streamlined EMsgComposer API. * plugins/mailing-list-actions/mailing-list-actions.c (emla_list_action_do): Adapt to streamlined EMsgComposer API. * po/POTFILES.in: Update file list for new composer. * ui/evolution-composer-entries.xml: Remove this file; obsoleted by new composer. * widgets/misc/Makefile.am: Add EVOLUTION_MAIL_LIBS. * widgets/misc/e-account-combo-box.c: * widgets/misc/e-account-combo-box.h: New function e_account_combo_box_get_account_list(). Emit a "refreshed" signal when the EAccountList changes. Add an internal reverse-lookup index. * widgets/misc/e-charset-picker.c (e_charser_add_radio_actions): New function adds radio actions to an action group. Will eventually replace e_charset_picker_bonobo_ui_populate(). * widgets/misc/e-signature-combo-box.c: * widgets/misc/e-signature-combo-box.h: New function e_signature_combo_box_get_signature_list(). ... separate issue ... * configure.in: Bump eds_minimum_version to 2.23.1 for CAMEL_FOLDER_JUNKED_NOT_DELETED symbol. svn path=/trunk/; revision=35313
Diffstat (limited to 'composer/e-composer-autosave.c')
-rw-r--r--composer/e-composer-autosave.c422
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;
+}