/* -*- 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 * * 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 #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 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; 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, ex); g_free (uid); camel_folder_free_message_info (folder, info); break; } case CAMEL_DISCO_DIARY_FOLDER_TRANSFER: { CamelFolder *source, *destination; GPtrArray *uids; guint32 delete_originals; 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, delete_originals, ex); 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); }