Add optimized indexing capabilities for phone number values.
[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_ref_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                 g_object_unref (session);
217
218                 fclose (diary->file);
219                 diary->file = NULL;
220         }
221 }
222
223 static void
224 free_uids (GPtrArray *array)
225 {
226         while (array->len--)
227                 g_free (array->pdata[array->len]);
228         g_ptr_array_free (array, TRUE);
229 }
230
231 static GPtrArray *
232 diary_decode_uids (CamelDiscoDiary *diary)
233 {
234         GPtrArray *uids;
235         gchar *uid;
236         guint32 i;
237
238         if (camel_file_util_decode_uint32 (diary->file, &i) == -1)
239                 return NULL;
240         uids = g_ptr_array_new ();
241         while (i--) {
242                 if (camel_file_util_decode_string (diary->file, &uid) == -1) {
243                         free_uids (uids);
244                         return NULL;
245                 }
246                 g_ptr_array_add (uids, uid);
247         }
248
249         return uids;
250 }
251
252 static CamelFolder *
253 diary_decode_folder (CamelDiscoDiary *diary,
254                      GCancellable *cancellable)
255 {
256         CamelFolder *folder;
257         gchar *name;
258
259         if (camel_file_util_decode_string (diary->file, &name) == -1)
260                 return NULL;
261         folder = g_hash_table_lookup (diary->folders, name);
262         if (!folder) {
263                 GError *error = NULL;
264                 gchar *msg;
265
266                 folder = camel_store_get_folder_sync (
267                         CAMEL_STORE (diary->store),
268                         name, 0, cancellable, &error);
269                 if (folder)
270                         g_hash_table_insert (diary->folders, name, folder);
271                 else {
272                         CamelService *service;
273                         CamelSession *session;
274
275                         service = CAMEL_SERVICE (diary->store);
276                         session = camel_service_ref_session (service);
277
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 (
284                                 session,
285                                 CAMEL_SESSION_ALERT_WARNING,
286                                 msg, NULL, cancellable);
287                         g_free (msg);
288
289                         g_object_unref (session);
290                         g_error_free (error);
291
292                         g_free (name);
293                 }
294         } else
295                 g_free (name);
296         return folder;
297 }
298
299 static void
300 close_folder (gchar *name,
301               CamelFolder *folder,
302               GCancellable *cancellable)
303 {
304         g_free (name);
305         camel_folder_synchronize_sync (folder, FALSE, cancellable, NULL);
306         g_object_unref (folder);
307 }
308
309 void
310 camel_disco_diary_replay (CamelDiscoDiary *diary,
311                           GCancellable *cancellable,
312                           GError **error)
313 {
314         guint32 action;
315         goffset size;
316         GError *local_error = NULL;
317
318         d (printf ("disco diary replay\n"));
319
320         fseek (diary->file, 0, SEEK_END);
321         size = ftell (diary->file);
322         g_return_if_fail (size != 0);
323         rewind (diary->file);
324
325         camel_operation_push_message (
326                 cancellable, _("Resynchronizing with server"));
327
328         while (local_error == NULL) {
329                 camel_operation_progress (
330                         cancellable, (ftell (diary->file) / size) * 100);
331
332                 if (camel_file_util_decode_uint32 (diary->file, &action) == -1)
333                         break;
334                 if (action == CAMEL_DISCO_DIARY_END)
335                         break;
336
337                 switch (action) {
338                 case CAMEL_DISCO_DIARY_FOLDER_EXPUNGE:
339                 {
340                         CamelFolder *folder;
341                         GPtrArray *uids;
342
343                         folder = diary_decode_folder (diary, cancellable);
344                         uids = diary_decode_uids (diary);
345                         if (!uids)
346                                 goto lose;
347
348                         if (folder)
349                                 camel_disco_folder_expunge_uids (
350                                         folder, uids, cancellable,
351                                         &local_error);
352                         free_uids (uids);
353                         break;
354                 }
355
356                 case CAMEL_DISCO_DIARY_FOLDER_APPEND:
357                 {
358                         CamelFolder *folder;
359                         gchar *uid, *ret_uid;
360                         CamelMimeMessage *message;
361                         CamelMessageInfo *info;
362
363                         folder = diary_decode_folder (diary, cancellable);
364                         if (camel_file_util_decode_string (diary->file, &uid) == -1)
365                                 goto lose;
366
367                         if (!folder) {
368                                 g_free (uid);
369                                 continue;
370                         }
371
372                         message = camel_folder_get_message_sync (
373                                 folder, uid, cancellable, NULL);
374                         if (!message) {
375                                 /* The message was appended and then deleted. */
376                                 g_free (uid);
377                                 continue;
378                         }
379                         info = camel_folder_get_message_info (folder, uid);
380
381                         camel_folder_append_message_sync (
382                                 folder, message, info, &ret_uid,
383                                 cancellable, &local_error);
384                         camel_folder_free_message_info (folder, info);
385
386                         if (ret_uid) {
387                                 camel_disco_diary_uidmap_add (diary, uid, ret_uid);
388                                 g_free (ret_uid);
389                         }
390                         g_free (uid);
391
392                         break;
393                 }
394
395                 case CAMEL_DISCO_DIARY_FOLDER_TRANSFER:
396                 {
397                         CamelFolder *source, *destination;
398                         GPtrArray *uids, *ret_uids;
399                         guint32 delete_originals;
400                         gint i;
401
402                         source = diary_decode_folder (diary, cancellable);
403                         destination = diary_decode_folder (diary, cancellable);
404                         uids = diary_decode_uids (diary);
405                         if (!uids)
406                                 goto lose;
407                         if (camel_file_util_decode_uint32 (diary->file, &delete_originals) == -1)
408                                 goto lose;
409
410                         if (!source || !destination) {
411                                 free_uids (uids);
412                                 continue;
413                         }
414
415                         camel_folder_transfer_messages_to_sync (
416                                 source, uids, destination, delete_originals,
417                                 &ret_uids, cancellable, &local_error);
418
419                         if (ret_uids) {
420                                 for (i = 0; i < uids->len; i++) {
421                                         if (!ret_uids->pdata[i])
422                                                 continue;
423                                         camel_disco_diary_uidmap_add (diary, uids->pdata[i], ret_uids->pdata[i]);
424                                         g_free (ret_uids->pdata[i]);
425                                 }
426                                 g_ptr_array_free (ret_uids, TRUE);
427                         }
428                         free_uids (uids);
429                         break;
430                 }
431
432                 }
433         }
434
435  lose:
436         camel_operation_pop_message (cancellable);
437
438         /* Close folders */
439         g_hash_table_foreach (
440                 diary->folders, (GHFunc) close_folder, cancellable);
441         g_hash_table_destroy (diary->folders);
442         diary->folders = NULL;
443
444         /* Truncate the log */
445         ftruncate (fileno (diary->file), 0);
446         rewind (diary->file);
447
448         g_propagate_error (error, local_error);
449 }
450
451 CamelDiscoDiary *
452 camel_disco_diary_new (CamelDiscoStore *store,
453                        const gchar *filename,
454                        GError **error)
455 {
456         CamelDiscoDiary *diary;
457
458         g_return_val_if_fail (CAMEL_IS_DISCO_STORE (store), NULL);
459         g_return_val_if_fail (filename != NULL, NULL);
460
461         diary = g_object_new (CAMEL_TYPE_DISCO_DIARY, NULL);
462         diary->store = store;
463
464         d (printf ("diary log file '%s'\n", filename));
465
466         /* Note that the linux man page says:
467          *
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.
471          *
472          * However, c99 (which glibc uses?) says:
473          * a+     append; open or create text file for update, writing at
474          *        end-of-file
475          *
476          * So we must seek ourselves.
477          */
478
479         diary->file = g_fopen (filename, "a+b");
480         if (!diary->file) {
481                 g_object_unref (diary);
482                 g_set_error (
483                         error, G_IO_ERROR,
484                         g_io_error_from_errno (errno),
485                         "Could not open journal file: %s",
486                         g_strerror (errno));
487                 return NULL;
488         }
489
490         fseek (diary->file, 0, SEEK_END);
491
492         d (printf (" is at %ld\n", ftell (diary->file)));
493
494         return diary;
495 }
496
497 gboolean
498 camel_disco_diary_empty (CamelDiscoDiary *diary)
499 {
500         return ftell (diary->file) == 0;
501 }
502
503 void
504 camel_disco_diary_uidmap_add (CamelDiscoDiary *diary,
505                               const gchar *old_uid,
506                               const gchar *new_uid)
507 {
508         g_hash_table_insert (
509                 diary->uidmap,
510                 g_strdup (old_uid),
511                 g_strdup (new_uid));
512 }
513
514 const gchar *
515 camel_disco_diary_uidmap_lookup (CamelDiscoDiary *diary,
516                                  const gchar *uid)
517 {
518         return g_hash_table_lookup (diary->uidmap, uid);
519 }