/*
* 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
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*/
#include "e-composer-autosave.h"
#include
#include
#include
#include
#include
#include
#include
#define AUTOSAVE_PREFIX ".evolution-composer.autosave"
#define AUTOSAVE_SEED AUTOSAVE_PREFIX "-XXXXXX"
#define AUTOSAVE_INTERVAL 60 /* 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_seconds (
AUTOSAVE_INTERVAL, (GSourceFunc)
composer_autosave_timeout, NULL);
}
void
e_composer_autosave_unregister (EMsgComposer *composer,
gboolean delete_file)
{
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;
close (state->fd);
if (delete_file)
g_unlink (state->filename);
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. */
/* do not touch "changed" flag, this is only autosave,
which doesn't mean it's saved permanently */
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;
}