Introduce CamelSession::trust_prompt()
[platform/upstream/evolution-data-server.git] / camel / camel-disco-diary.c
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 */
3
4 /*
5  * Authors: Dan Winship <danw@ximian.com>
6  *
7  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8  *
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.
12  *
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.
17  *
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
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #define __USE_LARGEFILE 1
29 #include <stdio.h>
30 #include <errno.h>
31
32 #include <glib/gstdio.h>
33 #include <glib/gi18n-lib.h>
34
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"
43
44 #define d(x)
45
46 G_DEFINE_TYPE (CamelDiscoDiary, camel_disco_diary, CAMEL_TYPE_OBJECT)
47
48 static void
49 unref_folder (gpointer key,
50               gpointer value,
51               gpointer data)
52 {
53         g_object_unref (value);
54 }
55
56 static void
57 free_uid (gpointer key,
58           gpointer value,
59           gpointer data)
60 {
61         g_free (key);
62         g_free (value);
63 }
64
65 static void
66 disco_diary_finalize (GObject *object)
67 {
68         CamelDiscoDiary *diary = CAMEL_DISCO_DIARY (object);
69
70         if (diary->file)
71                 fclose (diary->file);
72
73         if (diary->folders) {
74                 g_hash_table_foreach (diary->folders, unref_folder, NULL);
75                 g_hash_table_destroy (diary->folders);
76         }
77
78         if (diary->uidmap) {
79                 g_hash_table_foreach (diary->uidmap, free_uid, NULL);
80                 g_hash_table_destroy (diary->uidmap);
81         }
82
83         /* Chain up to parent's finalize() method. */
84         G_OBJECT_CLASS (camel_disco_diary_parent_class)->finalize (object);
85 }
86
87 static void
88 camel_disco_diary_class_init (CamelDiscoDiaryClass *class)
89 {
90         GObjectClass *object_class;
91
92         object_class = G_OBJECT_CLASS (class);
93         object_class->finalize = disco_diary_finalize;
94 }
95
96 static void
97 camel_disco_diary_init (CamelDiscoDiary *diary)
98 {
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);
101 }
102
103 static gint
104 diary_encode_uids (CamelDiscoDiary *diary,
105                    GPtrArray *uids)
106 {
107         gint i, status;
108
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]);
112         return status;
113 }
114
115 void
116 camel_disco_diary_log (CamelDiscoDiary *diary,
117                        CamelDiscoDiaryAction action,
118                        ...)
119 {
120         va_list ap;
121         gint status;
122
123         d (printf ("diary log: %s\n", diary->file?"ok":"no file!"));
124
125         /* You may already be a loser. */
126         if (!diary || !diary->file)
127                 return;
128
129         status = camel_file_util_encode_uint32 (diary->file, action);
130         if (status == -1)
131                 goto lose;
132
133         va_start (ap, action);
134         switch (action) {
135         case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
136         {
137                 CamelFolder *folder = va_arg (ap, CamelFolder *);
138                 GPtrArray *uids = va_arg (ap, GPtrArray *);
139                 const gchar *full_name;
140
141                 d (printf (" folder expunge '%s'\n", folder->full_name));
142
143                 full_name = camel_folder_get_full_name (folder);
144                 status = camel_file_util_encode_string (diary->file, full_name);
145                 if (status != -1)
146                         status = diary_encode_uids (diary, uids);
147                 break;
148         }
149
150         case CAMEL_DISCO_DIARY_FOLDER_APPEND:
151         {
152                 CamelFolder *folder = va_arg (ap, CamelFolder *);
153                 gchar *uid = va_arg (ap, gchar *);
154                 const gchar *full_name;
155
156                 d (printf (" folder append '%s'\n", folder->full_name));
157
158                 full_name = camel_folder_get_full_name (folder);
159                 status = camel_file_util_encode_string (diary->file, full_name);
160                 if (status != -1)
161                         status = camel_file_util_encode_string (diary->file, uid);
162                 break;
163         }
164
165         case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
166         {
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;
172
173                 full_name = camel_folder_get_full_name (source);
174                 status = camel_file_util_encode_string (diary->file, full_name);
175                 if (status == -1)
176                         break;
177
178                 full_name = camel_folder_get_full_name (destination);
179                 status = camel_file_util_encode_string (diary->file, full_name);
180                 if (status == -1)
181                         break;
182
183                 status = diary_encode_uids (diary, uids);
184                 if (status == -1)
185                         break;
186                 status = camel_file_util_encode_uint32 (diary->file, delete_originals);
187                 break;
188         }
189
190         default:
191                 g_assert_not_reached ();
192                 break;
193         }
194
195         va_end (ap);
196
197  lose:
198         if (status == -1) {
199                 CamelService *service;
200                 CamelSession *session;
201                 gchar *msg;
202
203                 service = CAMEL_SERVICE (diary->store);
204                 session = camel_service_get_session (service);
205
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."),
211                         g_strerror (errno));
212                 camel_session_alert_user (
213                         session, CAMEL_SESSION_ALERT_ERROR, msg, NULL, NULL);
214                 g_free (msg);
215
216                 fclose (diary->file);
217                 diary->file = NULL;
218         }
219 }
220
221 static void
222 free_uids (GPtrArray *array)
223 {
224         while (array->len--)
225                 g_free (array->pdata[array->len]);
226         g_ptr_array_free (array, TRUE);
227 }
228
229 static GPtrArray *
230 diary_decode_uids (CamelDiscoDiary *diary)
231 {
232         GPtrArray *uids;
233         gchar *uid;
234         guint32 i;
235
236         if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
237                 return NULL;
238         uids = g_ptr_array_new ();
239         while (i--) {
240                 if (camel_file_util_decode_string (diary->file, &uid) == -1) {
241                         free_uids (uids);
242                         return NULL;
243                 }
244                 g_ptr_array_add (uids, uid);
245         }
246
247         return uids;
248 }
249
250 static CamelFolder *
251 diary_decode_folder (CamelDiscoDiary *diary,
252                      GCancellable *cancellable)
253 {
254         CamelFolder *folder;
255         gchar *name;
256
257         if (camel_file_util_decode_string (diary->file, &name) == -1)
258                 return NULL;
259         folder = g_hash_table_lookup (diary->folders, name);
260         if (!folder) {
261                 GError *error = NULL;
262                 gchar *msg;
263
264                 folder = camel_store_get_folder_sync (
265                         CAMEL_STORE (diary->store),
266                         name, 0, cancellable, &error);
267                 if (folder)
268                         g_hash_table_insert (diary->folders, name, folder);
269                 else {
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);
280                         g_free (msg);
281                         g_free (name);
282                 }
283         } else
284                 g_free (name);
285         return folder;
286 }
287
288 static void
289 close_folder (gchar *name,
290               CamelFolder *folder,
291               GCancellable *cancellable)
292 {
293         g_free (name);
294         camel_folder_synchronize_sync (folder, FALSE, cancellable, NULL);
295         g_object_unref (folder);
296 }
297
298 void
299 camel_disco_diary_replay (CamelDiscoDiary *diary,
300                           GCancellable *cancellable,
301                           GError **error)
302 {
303         guint32 action;
304         goffset size;
305         GError *local_error = NULL;
306
307         d (printf ("disco diary replay\n"));
308
309         fseek (diary->file, 0, SEEK_END);
310         size = ftell (diary->file);
311         g_return_if_fail (size != 0);
312         rewind (diary->file);
313
314         camel_operation_push_message (
315                 cancellable, _("Resynchronizing with server"));
316
317         while (local_error == NULL) {
318                 camel_operation_progress (
319                         cancellable, (ftell (diary->file) / size) * 100);
320
321                 if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
322                         break;
323                 if (action == CAMEL_DISCO_DIARY_END)
324                         break;
325
326                 switch (action) {
327                 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
328                 {
329                         CamelFolder *folder;
330                         GPtrArray *uids;
331
332                         folder = diary_decode_folder (diary, cancellable);
333                         uids = diary_decode_uids (diary);
334                         if (!uids)
335                                 goto lose;
336
337                         if (folder)
338                                 camel_disco_folder_expunge_uids (
339                                         folder, uids, cancellable,
340                                         &local_error);
341                         free_uids (uids);
342                         break;
343                 }
344
345                 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
346                 {
347                         CamelFolder *folder;
348                         gchar *uid, *ret_uid;
349                         CamelMimeMessage *message;
350                         CamelMessageInfo *info;
351
352                         folder = diary_decode_folder (diary, cancellable);
353                         if (camel_file_util_decode_string (diary->file, &uid) == -1)
354                                 goto lose;
355
356                         if (!folder) {
357                                 g_free (uid);
358                                 continue;
359                         }
360
361                         message = camel_folder_get_message_sync (
362                                 folder, uid, cancellable, NULL);
363                         if (!message) {
364                                 /* The message was appended and then deleted. */
365                                 g_free (uid);
366                                 continue;
367                         }
368                         info = camel_folder_get_message_info (folder, uid);
369
370                         camel_folder_append_message_sync (
371                                 folder, message, info, &ret_uid,
372                                 cancellable, &local_error);
373                         camel_folder_free_message_info (folder, info);
374
375                         if (ret_uid) {
376                                 camel_disco_diary_uidmap_add (diary, uid, ret_uid);
377                                 g_free (ret_uid);
378                         }
379                         g_free (uid);
380
381                         break;
382                 }
383
384                 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
385                 {
386                         CamelFolder *source, *destination;
387                         GPtrArray *uids, *ret_uids;
388                         guint32 delete_originals;
389                         gint i;
390
391                         source = diary_decode_folder (diary, cancellable);
392                         destination = diary_decode_folder (diary, cancellable);
393                         uids = diary_decode_uids (diary);
394                         if (!uids)
395                                 goto lose;
396                         if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
397                                 goto lose;
398
399                         if (!source || !destination) {
400                                 free_uids (uids);
401                                 continue;
402                         }
403
404                         camel_folder_transfer_messages_to_sync (
405                                 source, uids, destination, delete_originals,
406                                 &ret_uids, cancellable, &local_error);
407
408                         if (ret_uids) {
409                                 for (i = 0; i < uids->len; i++) {
410                                         if (!ret_uids->pdata[i])
411                                                 continue;
412                                         camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
413                                         g_free (ret_uids->pdata[i]);
414                                 }
415                                 g_ptr_array_free (ret_uids, TRUE);
416                         }
417                         free_uids (uids);
418                         break;
419                 }
420
421                 }
422         }
423
424  lose:
425         camel_operation_pop_message (cancellable);
426
427         /* Close folders */
428         g_hash_table_foreach (
429                 diary->folders, (GHFunc) close_folder, cancellable);
430         g_hash_table_destroy (diary->folders);
431         diary->folders = NULL;
432
433         /* Truncate the log */
434         ftruncate (fileno (diary->file), 0);
435         rewind (diary->file);
436
437         g_propagate_error (error, local_error);
438 }
439
440 CamelDiscoDiary *
441 camel_disco_diary_new (CamelDiscoStore *store,
442                        const gchar *filename,
443                        GError **error)
444 {
445         CamelDiscoDiary *diary;
446
447         g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
448         g_return_val_if_fail (filename != NULL, NULL);
449
450         diary = g_object_new (CAMEL_TYPE_DISCO_DIARY, NULL);
451         diary->store = store;
452
453         d (printf ("diary log file '%s'\n", filename));
454
455         /* Note that the linux man page says:
456          *
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.
460          *
461          * However, c99 (which glibc uses?) says:
462          * a+     append; open or create text file for update, writing at
463          *        end-of-file
464          *
465          * So we must seek ourselves.
466          */
467
468         diary->file = g_fopen (filename, "a+b");
469         if (!diary->file) {
470                 g_object_unref (diary);
471                 g_set_error (
472                         error, G_IO_ERROR,
473                         g_io_error_from_errno (errno),
474                         "Could not open journal file: %s",
475                         g_strerror (errno));
476                 return NULL;
477         }
478
479         fseek (diary->file, 0, SEEK_END);
480
481         d (printf (" is at %ld\n", ftell (diary->file)));
482
483         return diary;
484 }
485
486 gboolean
487 camel_disco_diary_empty (CamelDiscoDiary *diary)
488 {
489         return ftell (diary->file) == 0;
490 }
491
492 void
493 camel_disco_diary_uidmap_add (CamelDiscoDiary *diary,
494                               const gchar *old_uid,
495                               const gchar *new_uid)
496 {
497         g_hash_table_insert (
498                 diary->uidmap,
499                 g_strdup (old_uid),
500                 g_strdup (new_uid));
501 }
502
503 const gchar *
504 camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary,
505                                  const gchar *uid)
506 {
507         return g_hash_table_lookup (diary->uidmap, uid);
508 }