/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-disco-diary.c: class for a disconnected operation log */
/*
* Authors: Dan Winship <danw@ximian.com>
*
* Copyright (C) 2001 Ximian, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU 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 General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "camel-disco-diary.h"
#include "camel-disco-folder.h"
#include "camel-disco-store.h"
#include "camel-exception.h"
#include "camel-file-utils.h"
#include "camel-folder.h"
#include "camel-operation.h"
#include "camel-session.h"
#include "camel-store.h"
#include <errno.h>
static void
camel_disco_diary_class_init (CamelDiscoDiaryClass *camel_disco_diary_class)
{
/* virtual method definition */
}
static void
camel_disco_diary_init (CamelDiscoDiary *diary)
{
diary->folders = g_hash_table_new (g_str_hash, g_str_equal);
diary->uidmap = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
unref_folder (gpointer key, gpointer value, gpointer data)
{
camel_object_unref (value);
}
static void
free_uid (gpointer key, gpointer value, gpointer data)
{
g_free (key);
g_free (value);
}
static void
camel_disco_diary_finalize (CamelDiscoDiary *diary)
{
if (diary->file)
fclose (diary->file);
if (diary->folders) {
g_hash_table_foreach (diary->folders, unref_folder, NULL);
g_hash_table_destroy (diary->folders);
}
if (diary->uidmap) {
g_hash_table_foreach (diary->uidmap, free_uid, NULL);
g_hash_table_destroy (diary->uidmap);
}
}
CamelType
camel_disco_diary_get_type (void)
{
static CamelType camel_disco_diary_type = CAMEL_INVALID_TYPE;
if (camel_disco_diary_type == CAMEL_INVALID_TYPE) {
camel_disco_diary_type = camel_type_register (
CAMEL_OBJECT_TYPE, "CamelDiscoDiary",
sizeof (CamelDiscoDiary),
sizeof (CamelDiscoDiaryClass),
(CamelObjectClassInitFunc) camel_disco_diary_class_init,
NULL,
(CamelObjectInitFunc) camel_disco_diary_init,
(CamelObjectFinalizeFunc) camel_disco_diary_finalize);
}
return camel_disco_diary_type;
}
static int
diary_encode_uids (CamelDiscoDiary *diary, GPtrArray *uids)
{
int i, status;
status = camel_file_util_encode_uint32 (diary->file, uids->len);
for (i = 0; status != -1 && i < uids->len; i++)
status = camel_file_util_encode_string (diary->file, uids->pdata[i]);
return status;
}
void
camel_disco_diary_log (CamelDiscoDiary *diary, CamelDiscoDiaryAction action,
...)
{
va_list ap;
int status;
/* You may already be a loser. */
if (!diary->file)
return;
status = camel_file_util_encode_uint32 (diary->file, action);
if (status == -1)
goto lose;
va_start (ap, action);
switch (action) {
case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
{
CamelFolder *folder = va_arg (ap, CamelFolder *);
GPtrArray *uids = va_arg (ap, GPtrArray *);
status = camel_file_util_encode_string (diary->file, folder->full_name);
if (status != -1)
status = diary_encode_uids (diary, uids);
break;
}
case CAMEL_DISCO_DIARY_FOLDER_APPEND:
{
CamelFolder *folder = va_arg (ap, CamelFolder *);
char *uid = va_arg (ap, char *);
status = camel_file_util_encode_string (diary->file, folder->full_name);
if (status != -1)
status = camel_file_util_encode_string (diary->file, uid);
break;
}
case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
{
CamelFolder *source = va_arg (ap, CamelFolder *);
CamelFolder *destination = va_arg (ap, CamelFolder *);
GPtrArray *uids = va_arg (ap, GPtrArray *);
gboolean delete_originals = va_arg (ap, gboolean);
status = camel_file_util_encode_string (diary->file, source->full_name);
if (status == -1)
break;
status = camel_file_util_encode_string (diary->file, destination->full_name);
if (status == -1)
break;
status = diary_encode_uids (diary, uids);
if (status == -1)
break;
status = camel_file_util_encode_uint32 (diary->file, delete_originals);
break;
}
default:
g_assert_not_reached ();
break;
}
va_end (ap);
lose:
if (status == -1) {
char *msg;
msg = g_strdup_printf (_("Could not write log entry: %s\n"
"Further operations on this server "
"will not be replayed when you\n"
"reconnect to the network."),
g_strerror (errno));
camel_session_alert_user (camel_service_get_session (CAMEL_SERVICE (diary->store)),
CAMEL_SESSION_ALERT_ERROR,
msg, FALSE);
g_free (msg);
fclose (diary->file);
diary->file = NULL;
}
}
static void
free_uids (GPtrArray *array)
{
while (array->len--)
g_free (array->pdata[array->len]);
g_ptr_array_free (array, TRUE);
}
static GPtrArray *
diary_decode_uids (CamelDiscoDiary *diary)
{
GPtrArray *uids;
char *uid;
guint32 i;
if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
return NULL;
uids = g_ptr_array_new ();
while (i--) {
if (camel_file_util_decode_string (diary->file, &uid) == -1) {
free_uids (uids);
return NULL;
}
g_ptr_array_add (uids, uid);
}
return uids;
}
static CamelFolder *
diary_decode_folder (CamelDiscoDiary *diary)
{
CamelFolder *folder;
char *name;
if (camel_file_util_decode_string (diary->file, &name) == -1)
return NULL;
folder = g_hash_table_lookup (diary->folders, name);
if (!folder) {
CamelException ex;
char *msg;
camel_exception_init (&ex);
folder = camel_store_get_folder (CAMEL_STORE (diary->store),
name, 0, &ex);
if (folder)
g_hash_table_insert (diary->folders, name, folder);
else {
msg = g_strdup_printf (_("Could not open `%s':\n%s\nChanges made to this folder will not be resynchronized."),
name, camel_exception_get_description (&ex));
camel_exception_clear (&ex);
camel_session_alert_user (camel_service_get_session (CAMEL_SERVICE (diary->store)),
CAMEL_SESSION_ALERT_WARNING,
msg, FALSE);
g_free (msg);
g_free (name);
}
} else
g_free (name);
return folder;
}
static void
close_folder (gpointer name, gpointer folder, gpointer data)
{
g_free (name);
camel_folder_sync (folder, FALSE, NULL);
camel_object_unref (folder);
}
void
camel_disco_diary_replay (CamelDiscoDiary *diary, CamelException *ex)
{
guint32 action;
off_t size;
double pc;
fseek (diary->file, 0, SEEK_END);
size = ftell (diary->file);
g_return_if_fail (size != 0);
rewind (diary->file);
camel_operation_start (NULL, _("Resynchronizing with server"));
while (!camel_exception_is_set (ex)) {
pc = ftell (diary->file) / size;
camel_operation_progress (NULL, pc * 100);
if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
break;
if (action == CAMEL_DISCO_DIARY_END)
break;
switch (action) {
case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
{
CamelFolder *folder;
GPtrArray *uids;
folder = diary_decode_folder (diary);
uids = diary_decode_uids (diary);
if (!uids)
goto lose;
if (folder)
camel_disco_folder_expunge_uids (folder, uids, ex);
free_uids (uids);
break;
}
case CAMEL_DISCO_DIARY_FOLDER_APPEND:
{
CamelFolder *folder;
char *uid, *ret_uid;
CamelMimeMessage *message;
CamelMessageInfo *info;
folder = diary_decode_folder (diary);
if (camel_file_util_decode_string (diary->file, &uid) == -1)
goto lose;
if (!folder) {
g_free (uid);
continue;
}
message = camel_folder_get_message (folder, uid, NULL);
if (!message) {
/* The message was appended and then deleted. */
g_free (uid);
continue;
}
info = camel_folder_get_message_info (folder, uid);
camel_folder_append_message (folder, message, info, &ret_uid, ex);
camel_folder_free_message_info (folder, info);
if (ret_uid) {
camel_disco_diary_uidmap_add (diary, uid, ret_uid);
g_free (ret_uid);
}
g_free (uid);
break;
}
case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
{
CamelFolder *source, *destination;
GPtrArray *uids, *ret_uids;
guint32 delete_originals;
int i;
source = diary_decode_folder (diary);
destination = diary_decode_folder (diary);
uids = diary_decode_uids (diary);
if (!uids)
goto lose;
if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
goto lose;
if (!source || !destination) {
free_uids (uids);
continue;
}
camel_folder_transfer_messages_to (source, uids, destination, &ret_uids, delete_originals, ex);
if (ret_uids) {
for (i = 0; i < uids->len; i++) {
if (!ret_uids->pdata[i])
continue;
camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
g_free (ret_uids->pdata[i]);
}
g_ptr_array_free (ret_uids, TRUE);
}
free_uids (uids);
break;
}
}
}
lose:
camel_operation_end (NULL);
/* Close folders */
g_hash_table_foreach (diary->folders, close_folder, diary);
g_hash_table_destroy (diary->folders);
diary->folders = NULL;
/* Truncate the log */
ftruncate (fileno (diary->file), 0);
}
CamelDiscoDiary *
camel_disco_diary_new (CamelDiscoStore *store, const char *filename, CamelException *ex)
{
CamelDiscoDiary *diary;
g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
g_return_val_if_fail (filename != NULL, NULL);
diary = CAMEL_DISCO_DIARY (camel_object_new (CAMEL_DISCO_DIARY_TYPE));
diary->store = store;
diary->file = fopen (filename, "a+");
if (!diary->file) {
camel_object_unref (CAMEL_OBJECT (diary));
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not open journal file: %s",
g_strerror (errno));
return NULL;
}
return diary;
}
gboolean
camel_disco_diary_empty (CamelDiscoDiary *diary)
{
return ftell (diary->file) == 0;
}
void
camel_disco_diary_uidmap_add (CamelDiscoDiary *diary, const char *old_uid,
const char *new_uid)
{
g_hash_table_insert (diary->uidmap, g_strdup (old_uid),
g_strdup (new_uid));
}
const char *
camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary, const char *uid)
{
return g_hash_table_lookup (diary->uidmap, uid);
}