diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2008-04-03 02:37:22 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@src.gnome.org> | 2008-04-03 02:37:22 +0800 |
commit | 16068d9b4191ea142a9e75a50eb8d260ed2bb406 (patch) | |
tree | 835a7909cd8b352d8c414986f1f5e27697b4de98 /composer/e-composer-autosave.c | |
parent | ee50e5d68e4f1a793541f1ee4979818ed4940173 (diff) | |
download | gsoc2013-evolution-16068d9b4191ea142a9e75a50eb8d260ed2bb406.tar.gz 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.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; +} |