Fix FSF address (Tobias Mueller, #470445)
[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) 2001 Ximian, Inc.
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.h>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35
36 #include "camel-disco-diary.h"
37 #include "camel-disco-folder.h"
38 #include "camel-disco-store.h"
39 #include "camel-exception.h"
40 #include "camel-file-utils.h"
41 #include "camel-folder.h"
42 #include "camel-operation.h"
43 #include "camel-session.h"
44 #include "camel-store.h"
45
46 #define d(x) 
47
48 static void
49 camel_disco_diary_class_init (CamelDiscoDiaryClass *camel_disco_diary_class)
50 {
51         /* virtual method definition */
52 }
53
54 static void
55 camel_disco_diary_init (CamelDiscoDiary *diary)
56 {
57         diary->folders = g_hash_table_new (g_str_hash, g_str_equal);
58         diary->uidmap = g_hash_table_new (g_str_hash, g_str_equal);
59 }
60
61 static void
62 unref_folder (gpointer key, gpointer value, gpointer data)
63 {
64         camel_object_unref (value);
65 }
66
67 static void
68 free_uid (gpointer key, gpointer value, gpointer data)
69 {
70         g_free (key);
71         g_free (value);
72 }
73
74 static void
75 camel_disco_diary_finalize (CamelDiscoDiary *diary)
76 {
77         if (diary->file)
78                 fclose (diary->file);
79         if (diary->folders) {
80                 g_hash_table_foreach (diary->folders, unref_folder, NULL);
81                 g_hash_table_destroy (diary->folders);
82         }
83         if (diary->uidmap) {
84                 g_hash_table_foreach (diary->uidmap, free_uid, NULL);
85                 g_hash_table_destroy (diary->uidmap);
86         }
87 }
88
89 CamelType
90 camel_disco_diary_get_type (void)
91 {
92         static CamelType camel_disco_diary_type = CAMEL_INVALID_TYPE;
93
94         if (camel_disco_diary_type == CAMEL_INVALID_TYPE) {
95                 camel_disco_diary_type = camel_type_register (
96                         CAMEL_OBJECT_TYPE, "CamelDiscoDiary",
97                         sizeof (CamelDiscoDiary),
98                         sizeof (CamelDiscoDiaryClass),
99                         (CamelObjectClassInitFunc) camel_disco_diary_class_init,
100                         NULL,
101                         (CamelObjectInitFunc) camel_disco_diary_init,
102                         (CamelObjectFinalizeFunc) camel_disco_diary_finalize);
103         }
104
105         return camel_disco_diary_type;
106 }
107
108
109 static int
110 diary_encode_uids (CamelDiscoDiary *diary, GPtrArray *uids)
111 {
112         int i, status;
113
114         status = camel_file_util_encode_uint32 (diary->file, uids->len);
115         for (i = 0; status != -1 && i < uids->len; i++)
116                 status = camel_file_util_encode_string (diary->file, uids->pdata[i]);
117         return status;
118 }
119
120 void
121 camel_disco_diary_log (CamelDiscoDiary *diary, CamelDiscoDiaryAction action,
122                        ...)
123 {
124         va_list ap;
125         int status;
126
127         d(printf("diary log: %s\n", diary->file?"ok":"no file!"));
128
129         /* You may already be a loser. */
130         if (!diary && !diary->file)
131                 return;
132
133         status = camel_file_util_encode_uint32 (diary->file, action);
134         if (status == -1)
135                 goto lose;
136
137         va_start (ap, action);
138         switch (action) {
139         case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
140         {
141                 CamelFolder *folder = va_arg (ap, CamelFolder *);
142                 GPtrArray *uids = va_arg (ap, GPtrArray *);
143
144                 d(printf(" folder expunge '%s'\n", folder->full_name));
145
146                 status = camel_file_util_encode_string (diary->file, folder->full_name);
147                 if (status != -1)
148                         status = diary_encode_uids (diary, uids);
149                 break;
150         }
151
152         case CAMEL_DISCO_DIARY_FOLDER_APPEND:
153         {
154                 CamelFolder *folder = va_arg (ap, CamelFolder *);
155                 char *uid = va_arg (ap, char *);
156
157                 d(printf(" folder append '%s'\n", folder->full_name));
158
159                 status = camel_file_util_encode_string (diary->file, folder->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
172                 d(printf(" folder transfer '%s' to '%s'\n", source->full_name, destination->full_name));
173
174                 status = camel_file_util_encode_string (diary->file, source->full_name);
175                 if (status == -1)
176                         break;
177                 status = camel_file_util_encode_string (diary->file, destination->full_name);
178                 if (status == -1)
179                         break;
180                 status = diary_encode_uids (diary, uids);
181                 if (status == -1)
182                         break;
183                 status = camel_file_util_encode_uint32 (diary->file, delete_originals);
184                 break;
185         }
186
187         default:
188                 g_assert_not_reached ();
189                 break;
190         }
191
192         va_end (ap);
193
194  lose:
195         if (status == -1) {
196                 char *msg;
197
198                 msg = g_strdup_printf (_("Could not write log entry: %s\n"
199                                          "Further operations on this server "
200                                          "will not be replayed when you\n"
201                                          "reconnect to the network."),
202                                        g_strerror (errno));
203                 camel_session_alert_user (camel_service_get_session (CAMEL_SERVICE (diary->store)),
204                                           CAMEL_SESSION_ALERT_ERROR,
205                                           msg, FALSE);
206                 g_free (msg);
207
208                 fclose (diary->file);
209                 diary->file = NULL;
210         }
211 }
212
213 static void
214 free_uids (GPtrArray *array)
215 {
216         while (array->len--)
217                 g_free (array->pdata[array->len]);
218         g_ptr_array_free (array, TRUE);
219 }
220
221 static GPtrArray *
222 diary_decode_uids (CamelDiscoDiary *diary)
223 {
224         GPtrArray *uids;
225         char *uid;
226         guint32 i;
227
228         if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
229                 return NULL;
230         uids = g_ptr_array_new ();
231         while (i--) {
232                 if (camel_file_util_decode_string (diary->file, &uid) == -1) {
233                         free_uids (uids);
234                         return NULL;
235                 }
236                 g_ptr_array_add (uids, uid);
237         }
238
239         return uids;
240 }
241
242 static CamelFolder *
243 diary_decode_folder (CamelDiscoDiary *diary)
244 {
245         CamelFolder *folder;
246         char *name;
247
248         if (camel_file_util_decode_string (diary->file, &name) == -1)
249                 return NULL;
250         folder = g_hash_table_lookup (diary->folders, name);
251         if (!folder) {
252                 CamelException ex;
253                 char *msg;
254
255                 camel_exception_init (&ex);
256                 folder = camel_store_get_folder (CAMEL_STORE (diary->store),
257                                                  name, 0, &ex);
258                 if (folder)
259                         g_hash_table_insert (diary->folders, name, folder);
260                 else {
261                         msg = g_strdup_printf (_("Could not open `%s':\n%s\nChanges made to this folder will not be resynchronized."),
262                                                name, camel_exception_get_description (&ex));
263                         camel_exception_clear (&ex);
264                         camel_session_alert_user (camel_service_get_session (CAMEL_SERVICE (diary->store)),
265                                                   CAMEL_SESSION_ALERT_WARNING,
266                                                   msg, FALSE);
267                         g_free (msg);
268                         g_free (name);
269                 }
270         } else
271                 g_free (name);
272         return folder;
273 }
274
275 static void
276 close_folder (gpointer name, gpointer folder, gpointer data)
277 {
278         g_free (name);
279         camel_folder_sync (folder, FALSE, NULL);
280         camel_object_unref (folder);
281 }
282
283 void
284 camel_disco_diary_replay (CamelDiscoDiary *diary, CamelException *ex)
285 {
286         guint32 action;
287         off_t size;
288         double pc;
289
290         d(printf("disco diary replay\n"));
291
292         fseek (diary->file, 0, SEEK_END);
293         size = ftell (diary->file);
294         g_return_if_fail (size != 0);
295         rewind (diary->file);
296
297         camel_operation_start (NULL, _("Resynchronizing with server"));
298         while (!camel_exception_is_set (ex)) {
299                 pc = ftell (diary->file) / size;
300                 camel_operation_progress (NULL, pc * 100);
301
302                 if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
303                         break;
304                 if (action == CAMEL_DISCO_DIARY_END)
305                         break;
306
307                 switch (action) {
308                 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
309                 {
310                         CamelFolder *folder;
311                         GPtrArray *uids;
312
313                         folder = diary_decode_folder (diary);
314                         uids = diary_decode_uids (diary);
315                         if (!uids)
316                                 goto lose;
317
318                         if (folder)
319                                 camel_disco_folder_expunge_uids (folder, uids, ex);
320                         free_uids (uids);
321                         break;
322                 }
323
324                 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
325                 {
326                         CamelFolder *folder;
327                         char *uid, *ret_uid;
328                         CamelMimeMessage *message;
329                         CamelMessageInfo *info;
330
331                         folder = diary_decode_folder (diary);
332                         if (camel_file_util_decode_string (diary->file, &uid) == -1)
333                                 goto lose;
334
335                         if (!folder) {
336                                 g_free (uid);
337                                 continue;
338                         }
339
340                         message = camel_folder_get_message (folder, uid, NULL);
341                         if (!message) {
342                                 /* The message was appended and then deleted. */
343                                 g_free (uid);
344                                 continue;
345                         }
346                         info = camel_folder_get_message_info (folder, uid);
347
348                         camel_folder_append_message (folder, message, info, &ret_uid, ex);
349                         camel_folder_free_message_info (folder, info);
350
351                         if (ret_uid) {
352                                 camel_disco_diary_uidmap_add (diary, uid, ret_uid);
353                                 g_free (ret_uid);
354                         }
355                         g_free (uid);
356
357                         break;
358                 }
359
360                 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
361                 {
362                         CamelFolder *source, *destination;
363                         GPtrArray *uids, *ret_uids;
364                         guint32 delete_originals;
365                         int i;
366
367                         source = diary_decode_folder (diary);
368                         destination = diary_decode_folder (diary);
369                         uids = diary_decode_uids (diary);
370                         if (!uids)
371                                 goto lose;
372                         if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
373                                 goto lose;
374
375                         if (!source || !destination) {
376                                 free_uids (uids);
377                                 continue;
378                         }
379
380                         camel_folder_transfer_messages_to (source, uids, destination, &ret_uids, delete_originals, ex);
381
382                         if (ret_uids) {
383                                 for (i = 0; i < uids->len; i++) {
384                                         if (!ret_uids->pdata[i])
385                                                 continue;
386                                         camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
387                                         g_free (ret_uids->pdata[i]);
388                                 }
389                                 g_ptr_array_free (ret_uids, TRUE);
390                         }
391                         free_uids (uids);
392                         break;
393                 }
394
395                 }
396         }
397
398  lose:
399         camel_operation_end (NULL);
400
401         /* Close folders */
402         g_hash_table_foreach (diary->folders, close_folder, diary);
403         g_hash_table_destroy (diary->folders);
404         diary->folders = NULL;
405
406         /* Truncate the log */
407         ftruncate (fileno (diary->file), 0);
408 }
409
410 CamelDiscoDiary *
411 camel_disco_diary_new (CamelDiscoStore *store, const char *filename, CamelException *ex)
412 {
413         CamelDiscoDiary *diary;
414
415         g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
416         g_return_val_if_fail (filename != NULL, NULL);
417
418         diary = CAMEL_DISCO_DIARY (camel_object_new (CAMEL_DISCO_DIARY_TYPE));
419         diary->store = store;
420
421         d(printf("diary log file '%s'\n", filename));
422
423         /* Note that the linux man page says:
424
425            a+     Open for reading and appending (writing at end  of  file).   The
426                   file  is created if it does not exist.  The stream is positioned
427                   at the end of the file.
428            However, c99 (which glibc uses?) says:
429            a+     append; open or create text file for update, writing at
430                    end-of-file
431
432            So we must seek ourselves.
433         */
434
435         diary->file = g_fopen (filename, "a+b");
436         if (!diary->file) {
437                 camel_object_unref (diary);
438                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
439                                       "Could not open journal file: %s",
440                                       g_strerror (errno));
441                 return NULL;
442         }
443
444         fseek(diary->file, 0, SEEK_END);
445
446         d(printf(" is at %ld\n", ftell(diary->file)));
447
448         return diary;
449 }
450
451 gboolean
452 camel_disco_diary_empty  (CamelDiscoDiary *diary)
453 {
454         return ftell (diary->file) == 0;
455 }
456
457 void
458 camel_disco_diary_uidmap_add (CamelDiscoDiary *diary, const char *old_uid,
459                               const char *new_uid)
460 {
461         g_hash_table_insert (diary->uidmap, g_strdup (old_uid),
462                              g_strdup (new_uid));
463 }
464
465 const char *
466 camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary, const char *uid)
467 {
468         return g_hash_table_lookup (diary->uidmap, uid);
469 }