1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-disco-diary.c: class for a disconnected operation log */
5 * Authors: Dan Winship <danw@ximian.com>
7 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU Lesser General Public
11 * License as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
28 #define __USE_LARGEFILE 1
32 #include <glib/gstdio.h>
33 #include <glib/gi18n-lib.h>
35 #include "camel-disco-diary.h"
36 #include "camel-disco-folder.h"
37 #include "camel-disco-store.h"
38 #include "camel-file-utils.h"
39 #include "camel-folder.h"
40 #include "camel-operation.h"
41 #include "camel-session.h"
42 #include "camel-store.h"
46 G_DEFINE_TYPE (CamelDiscoDiary, camel_disco_diary, CAMEL_TYPE_OBJECT)
49 unref_folder (gpointer key,
53 g_object_unref (value);
57 free_uid (gpointer key,
66 disco_diary_finalize (GObject *object)
68 CamelDiscoDiary *diary = CAMEL_DISCO_DIARY (object);
74 g_hash_table_foreach (diary->folders, unref_folder, NULL);
75 g_hash_table_destroy (diary->folders);
79 g_hash_table_foreach (diary->uidmap, free_uid, NULL);
80 g_hash_table_destroy (diary->uidmap);
83 /* Chain up to parent's finalize() method. */
84 G_OBJECT_CLASS (camel_disco_diary_parent_class)->finalize (object);
88 camel_disco_diary_class_init (CamelDiscoDiaryClass *class)
90 GObjectClass *object_class;
92 object_class = G_OBJECT_CLASS (class);
93 object_class->finalize = disco_diary_finalize;
97 camel_disco_diary_init (CamelDiscoDiary *diary)
99 diary->folders = g_hash_table_new (g_str_hash, g_str_equal);
100 diary->uidmap = g_hash_table_new (g_str_hash, g_str_equal);
104 diary_encode_uids (CamelDiscoDiary *diary,
109 status = camel_file_util_encode_uint32 (diary->file, uids->len);
110 for (i = 0; status != -1 && i < uids->len; i++)
111 status = camel_file_util_encode_string (diary->file, uids->pdata[i]);
116 camel_disco_diary_log (CamelDiscoDiary *diary,
117 CamelDiscoDiaryAction action,
123 d (printf ("diary log: %s\n", diary->file?"ok":"no file!"));
125 /* You may already be a loser. */
126 if (!diary || !diary->file)
129 status = camel_file_util_encode_uint32 (diary->file, action);
133 va_start (ap, action);
135 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
137 CamelFolder *folder = va_arg (ap, CamelFolder *);
138 GPtrArray *uids = va_arg (ap, GPtrArray *);
139 const gchar *full_name;
141 d (printf (" folder expunge '%s'\n", folder->full_name));
143 full_name = camel_folder_get_full_name (folder);
144 status = camel_file_util_encode_string (diary->file, full_name);
146 status = diary_encode_uids (diary, uids);
150 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
152 CamelFolder *folder = va_arg (ap, CamelFolder *);
153 gchar *uid = va_arg (ap, gchar *);
154 const gchar *full_name;
156 d (printf (" folder append '%s'\n", folder->full_name));
158 full_name = camel_folder_get_full_name (folder);
159 status = camel_file_util_encode_string (diary->file, full_name);
161 status = camel_file_util_encode_string (diary->file, uid);
165 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
167 CamelFolder *source = va_arg (ap, CamelFolder *);
168 CamelFolder *destination = va_arg (ap, CamelFolder *);
169 GPtrArray *uids = va_arg (ap, GPtrArray *);
170 gboolean delete_originals = va_arg (ap, gboolean);
171 const gchar *full_name;
173 full_name = camel_folder_get_full_name (source);
174 status = camel_file_util_encode_string (diary->file, full_name);
178 full_name = camel_folder_get_full_name (destination);
179 status = camel_file_util_encode_string (diary->file, full_name);
183 status = diary_encode_uids (diary, uids);
186 status = camel_file_util_encode_uint32 (diary->file, delete_originals);
191 g_assert_not_reached ();
199 CamelService *service;
200 CamelSession *session;
203 service = CAMEL_SERVICE (diary->store);
204 session = camel_service_get_session (service);
206 msg = g_strdup_printf (
207 _("Could not write log entry: %s\n"
208 "Further operations on this server "
209 "will not be replayed when you\n"
210 "reconnect to the network."),
212 camel_session_alert_user (
213 session, CAMEL_SESSION_ALERT_ERROR, msg, NULL, NULL);
216 fclose (diary->file);
222 free_uids (GPtrArray *array)
225 g_free (array->pdata[array->len]);
226 g_ptr_array_free (array, TRUE);
230 diary_decode_uids (CamelDiscoDiary *diary)
236 if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
238 uids = g_ptr_array_new ();
240 if (camel_file_util_decode_string (diary->file, &uid) == -1) {
244 g_ptr_array_add (uids, uid);
251 diary_decode_folder (CamelDiscoDiary *diary,
252 GCancellable *cancellable)
257 if (camel_file_util_decode_string (diary->file, &name) == -1)
259 folder = g_hash_table_lookup (diary->folders, name);
261 GError *error = NULL;
264 folder = camel_store_get_folder_sync (
265 CAMEL_STORE (diary->store),
266 name, 0, cancellable, &error);
268 g_hash_table_insert (diary->folders, name, folder);
270 msg = g_strdup_printf (
271 _("Could not open '%s':\n%s\n"
272 "Changes made to this folder "
273 "will not be resynchronized."),
274 name, error->message);
275 g_error_free (error);
276 camel_session_alert_user (
277 camel_service_get_session (CAMEL_SERVICE (diary->store)),
278 CAMEL_SESSION_ALERT_WARNING,
279 msg, NULL, cancellable);
289 close_folder (gchar *name,
291 GCancellable *cancellable)
294 camel_folder_synchronize_sync (folder, FALSE, cancellable, NULL);
295 g_object_unref (folder);
299 camel_disco_diary_replay (CamelDiscoDiary *diary,
300 GCancellable *cancellable,
305 GError *local_error = NULL;
307 d (printf ("disco diary replay\n"));
309 fseek (diary->file, 0, SEEK_END);
310 size = ftell (diary->file);
311 g_return_if_fail (size != 0);
312 rewind (diary->file);
314 camel_operation_push_message (
315 cancellable, _("Resynchronizing with server"));
317 while (local_error == NULL) {
318 camel_operation_progress (
319 cancellable, (ftell (diary->file) / size) * 100);
321 if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
323 if (action == CAMEL_DISCO_DIARY_END)
327 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
332 folder = diary_decode_folder (diary, cancellable);
333 uids = diary_decode_uids (diary);
338 camel_disco_folder_expunge_uids (
339 folder, uids, cancellable,
345 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
348 gchar *uid, *ret_uid;
349 CamelMimeMessage *message;
350 CamelMessageInfo *info;
352 folder = diary_decode_folder (diary, cancellable);
353 if (camel_file_util_decode_string (diary->file, &uid) == -1)
361 message = camel_folder_get_message_sync (
362 folder, uid, cancellable, NULL);
364 /* The message was appended and then deleted. */
368 info = camel_folder_get_message_info (folder, uid);
370 camel_folder_append_message_sync (
371 folder, message, info, &ret_uid,
372 cancellable, &local_error);
373 camel_folder_free_message_info (folder, info);
376 camel_disco_diary_uidmap_add (diary, uid, ret_uid);
384 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
386 CamelFolder *source, *destination;
387 GPtrArray *uids, *ret_uids;
388 guint32 delete_originals;
391 source = diary_decode_folder (diary, cancellable);
392 destination = diary_decode_folder (diary, cancellable);
393 uids = diary_decode_uids (diary);
396 if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
399 if (!source || !destination) {
404 camel_folder_transfer_messages_to_sync (
405 source, uids, destination, delete_originals,
406 &ret_uids, cancellable, &local_error);
409 for (i = 0; i < uids->len; i++) {
410 if (!ret_uids->pdata[i])
412 camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
413 g_free (ret_uids->pdata[i]);
415 g_ptr_array_free (ret_uids, TRUE);
425 camel_operation_pop_message (cancellable);
428 g_hash_table_foreach (
429 diary->folders, (GHFunc) close_folder, cancellable);
430 g_hash_table_destroy (diary->folders);
431 diary->folders = NULL;
433 /* Truncate the log */
434 ftruncate (fileno (diary->file), 0);
435 rewind (diary->file);
437 g_propagate_error (error, local_error);
441 camel_disco_diary_new (CamelDiscoStore *store,
442 const gchar *filename,
445 CamelDiscoDiary *diary;
447 g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
448 g_return_val_if_fail (filename != NULL, NULL);
450 diary = g_object_new (CAMEL_TYPE_DISCO_DIARY, NULL);
451 diary->store = store;
453 d (printf ("diary log file '%s'\n", filename));
455 /* Note that the linux man page says:
457 * a+ Open for reading and appending (writing at end of file).
458 * The file is created if it does not exist. The stream is
459 * positioned at the end of the file.
461 * However, c99 (which glibc uses?) says:
462 * a+ append; open or create text file for update, writing at
465 * So we must seek ourselves.
468 diary->file = g_fopen (filename, "a+b");
470 g_object_unref (diary);
473 g_io_error_from_errno (errno),
474 "Could not open journal file: %s",
479 fseek (diary->file, 0, SEEK_END);
481 d (printf (" is at %ld\n", ftell (diary->file)));
487 camel_disco_diary_empty (CamelDiscoDiary *diary)
489 return ftell (diary->file) == 0;
493 camel_disco_diary_uidmap_add (CamelDiscoDiary *diary,
494 const gchar *old_uid,
495 const gchar *new_uid)
497 g_hash_table_insert (
504 camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary,
507 return g_hash_table_lookup (diary->uidmap, uid);