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_ref_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 g_object_unref (session);
218 fclose (diary->file);
224 free_uids (GPtrArray *array)
227 g_free (array->pdata[array->len]);
228 g_ptr_array_free (array, TRUE);
232 diary_decode_uids (CamelDiscoDiary *diary)
238 if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
240 uids = g_ptr_array_new ();
242 if (camel_file_util_decode_string (diary->file, &uid) == -1) {
246 g_ptr_array_add (uids, uid);
253 diary_decode_folder (CamelDiscoDiary *diary,
254 GCancellable *cancellable)
259 if (camel_file_util_decode_string (diary->file, &name) == -1)
261 folder = g_hash_table_lookup (diary->folders, name);
263 GError *error = NULL;
266 folder = camel_store_get_folder_sync (
267 CAMEL_STORE (diary->store),
268 name, 0, cancellable, &error);
270 g_hash_table_insert (diary->folders, name, folder);
272 CamelService *service;
273 CamelSession *session;
275 service = CAMEL_SERVICE (diary->store);
276 session = camel_service_ref_session (service);
278 msg = g_strdup_printf (
279 _("Could not open '%s':\n%s\n"
280 "Changes made to this folder "
281 "will not be resynchronized."),
282 name, error->message);
283 camel_session_alert_user (
285 CAMEL_SESSION_ALERT_WARNING,
286 msg, NULL, cancellable);
289 g_object_unref (session);
290 g_error_free (error);
300 close_folder (gchar *name,
302 GCancellable *cancellable)
305 camel_folder_synchronize_sync (folder, FALSE, cancellable, NULL);
306 g_object_unref (folder);
310 camel_disco_diary_replay (CamelDiscoDiary *diary,
311 GCancellable *cancellable,
316 GError *local_error = NULL;
318 d (printf ("disco diary replay\n"));
320 fseek (diary->file, 0, SEEK_END);
321 size = ftell (diary->file);
322 g_return_if_fail (size != 0);
323 rewind (diary->file);
325 camel_operation_push_message (
326 cancellable, _("Resynchronizing with server"));
328 while (local_error == NULL) {
329 camel_operation_progress (
330 cancellable, (ftell (diary->file) / size) * 100);
332 if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
334 if (action == CAMEL_DISCO_DIARY_END)
338 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
343 folder = diary_decode_folder (diary, cancellable);
344 uids = diary_decode_uids (diary);
349 camel_disco_folder_expunge_uids (
350 folder, uids, cancellable,
356 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
359 gchar *uid, *ret_uid;
360 CamelMimeMessage *message;
361 CamelMessageInfo *info;
363 folder = diary_decode_folder (diary, cancellable);
364 if (camel_file_util_decode_string (diary->file, &uid) == -1)
372 message = camel_folder_get_message_sync (
373 folder, uid, cancellable, NULL);
375 /* The message was appended and then deleted. */
379 info = camel_folder_get_message_info (folder, uid);
381 camel_folder_append_message_sync (
382 folder, message, info, &ret_uid,
383 cancellable, &local_error);
384 camel_folder_free_message_info (folder, info);
387 camel_disco_diary_uidmap_add (diary, uid, ret_uid);
395 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
397 CamelFolder *source, *destination;
398 GPtrArray *uids, *ret_uids;
399 guint32 delete_originals;
402 source = diary_decode_folder (diary, cancellable);
403 destination = diary_decode_folder (diary, cancellable);
404 uids = diary_decode_uids (diary);
407 if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
410 if (!source || !destination) {
415 camel_folder_transfer_messages_to_sync (
416 source, uids, destination, delete_originals,
417 &ret_uids, cancellable, &local_error);
420 for (i = 0; i < uids->len; i++) {
421 if (!ret_uids->pdata[i])
423 camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
424 g_free (ret_uids->pdata[i]);
426 g_ptr_array_free (ret_uids, TRUE);
436 camel_operation_pop_message (cancellable);
439 g_hash_table_foreach (
440 diary->folders, (GHFunc) close_folder, cancellable);
441 g_hash_table_destroy (diary->folders);
442 diary->folders = NULL;
444 /* Truncate the log */
445 ftruncate (fileno (diary->file), 0);
446 rewind (diary->file);
448 g_propagate_error (error, local_error);
452 camel_disco_diary_new (CamelDiscoStore *store,
453 const gchar *filename,
456 CamelDiscoDiary *diary;
458 g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
459 g_return_val_if_fail (filename != NULL, NULL);
461 diary = g_object_new (CAMEL_TYPE_DISCO_DIARY, NULL);
462 diary->store = store;
464 d (printf ("diary log file '%s'\n", filename));
466 /* Note that the linux man page says:
468 * a+ Open for reading and appending (writing at end of file).
469 * The file is created if it does not exist. The stream is
470 * positioned at the end of the file.
472 * However, c99 (which glibc uses?) says:
473 * a+ append; open or create text file for update, writing at
476 * So we must seek ourselves.
479 diary->file = g_fopen (filename, "a+b");
481 g_object_unref (diary);
484 g_io_error_from_errno (errno),
485 "Could not open journal file: %s",
490 fseek (diary->file, 0, SEEK_END);
492 d (printf (" is at %ld\n", ftell (diary->file)));
498 camel_disco_diary_empty (CamelDiscoDiary *diary)
500 return ftell (diary->file) == 0;
504 camel_disco_diary_uidmap_add (CamelDiscoDiary *diary,
505 const gchar *old_uid,
506 const gchar *new_uid)
508 g_hash_table_insert (
515 camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary,
518 return g_hash_table_lookup (diary->uidmap, uid);