Update API documentation.
[platform/upstream/evolution-data-server.git] / camel / camel-folder-summary.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <ctype.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <errno.h>
34
35 #include <glib/gi18n-lib.h>
36 #include <glib/gstdio.h>
37
38 #include "camel-db.h"
39 #include "camel-debug.h"
40 #include "camel-file-utils.h"
41 #include "camel-folder-summary.h"
42 #include "camel-folder.h"
43 #include "camel-iconv.h"
44 #include "camel-mime-filter-basic.h"
45 #include "camel-mime-filter-charset.h"
46 #include "camel-mime-filter-html.h"
47 #include "camel-mime-filter-index.h"
48 #include "camel-mime-filter.h"
49 #include "camel-mime-message.h"
50 #include "camel-multipart.h"
51 #include "camel-session.h"
52 #include "camel-stream-filter.h"
53 #include "camel-stream-mem.h"
54 #include "camel-stream-null.h"
55 #include "camel-string-utils.h"
56 #include "camel-store.h"
57 #include "camel-vee-folder.h"
58 #include "camel-vtrash-folder.h"
59 #include "camel-mime-part-utils.h"
60
61 #define CAMEL_FOLDER_SUMMARY_GET_PRIVATE(obj) \
62         (G_TYPE_INSTANCE_GET_PRIVATE \
63         ((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummaryPrivate))
64
65 /* Make 5 minutes as default cache drop */
66 #define SUMMARY_CACHE_DROP 300
67 #define dd(x) if (camel_debug("sync")) x
68
69 struct _CamelFolderSummaryPrivate {
70         GHashTable *filter_charset;     /* CamelMimeFilterCharset's indexed by source charset */
71
72         struct _CamelMimeFilter *filter_index;
73         struct _CamelMimeFilter *filter_64;
74         struct _CamelMimeFilter *filter_qp;
75         struct _CamelMimeFilter *filter_uu;
76         struct _CamelMimeFilter *filter_save;
77         struct _CamelMimeFilter *filter_html;
78
79         struct _CamelStream *filter_stream;
80
81         struct _CamelIndex *index;
82
83         GStaticRecMutex summary_lock;   /* for the summary hashtable/array */
84         GStaticRecMutex io_lock;        /* load/save lock, for access to saved_count, etc */
85         GStaticRecMutex filter_lock;    /* for accessing any of the filtering/indexing stuff, since we share them */
86         GStaticRecMutex alloc_lock;     /* for setting up and using allocators */
87         GStaticRecMutex ref_lock;       /* for reffing/unreffing messageinfo's ALWAYS obtain before summary_lock */
88
89         gboolean need_preview;
90         GHashTable *preview_updates;
91
92         guint32 nextuid;        /* next uid? */
93         guint32 saved_count;    /* how many were saved/loaded */
94         guint32 unread_count;   /* handy totals */
95         guint32 deleted_count;
96         guint32 junk_count;
97         guint32 junk_not_deleted_count;
98         guint32 visible_count;
99
100         gboolean build_content; /* do we try and parse/index the content, or not? */
101
102         GHashTable *uids; /* uids of all known message infos; the 'value' are used flags for the message info */
103         GHashTable *loaded_infos; /* uid->CamelMessageInfo *, those currently in memory */
104
105         struct _CamelFolder *folder; /* parent folder, for events */
106         time_t cache_load_time;
107         guint timeout_handle;
108 };
109
110 static GStaticMutex info_lock = G_STATIC_MUTEX_INIT;
111
112 /* this lock is ONLY for the standalone messageinfo stuff */
113 #define GLOBAL_INFO_LOCK(i) g_static_mutex_lock(&info_lock)
114 #define GLOBAL_INFO_UNLOCK(i) g_static_mutex_unlock(&info_lock)
115
116 /* this should probably be conditional on it existing */
117 #define USE_BSEARCH
118
119 #define d(x)
120 #define io(x)                   /* io debug */
121 #define w(x)
122
123 #define CAMEL_FOLDER_SUMMARY_VERSION (14)
124
125 /* trivial lists, just because ... */
126 struct _node {
127         struct _node *next;
128 };
129
130 static void cfs_schedule_info_release_timer (CamelFolderSummary *summary);
131
132 static struct _node *my_list_append (struct _node **list, struct _node *n);
133 static gint my_list_size (struct _node **list);
134
135 static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
136 static CamelMessageInfo * message_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
137 static CamelMessageInfo * message_info_new_from_message (CamelFolderSummary *summary, CamelMimeMessage *msg, const gchar *bodystructure);
138 static void               message_info_free (CamelFolderSummary *, CamelMessageInfo *);
139
140 static CamelMessageContentInfo * content_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
141 static CamelMessageContentInfo * content_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
142 static CamelMessageContentInfo * content_info_new_from_message (CamelFolderSummary *summary, CamelMimePart *mp);
143 static void                      content_info_free (CamelFolderSummary *, CamelMessageContentInfo *);
144
145 static gint save_message_infos_to_db (CamelFolderSummary *summary, gboolean fresh_mir, GError **error);
146 static gint camel_read_mir_callback (gpointer  ref, gint ncol, gchar ** cols, gchar ** name);
147
148 static gchar *next_uid_string (CamelFolderSummary *summary);
149
150 static CamelMessageContentInfo * summary_build_content_info (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimeParser *mp);
151 static CamelMessageContentInfo * summary_build_content_info_message (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimePart *object);
152
153 static CamelMessageInfo * message_info_from_uid (CamelFolderSummary *summary, const gchar *uid);
154
155 enum {
156         PROP_0,
157         PROP_FOLDER,
158         PROP_SAVED_COUNT,
159         PROP_UNREAD_COUNT,
160         PROP_DELETED_COUNT,
161         PROP_JUNK_COUNT,
162         PROP_JUNK_NOT_DELETED_COUNT,
163         PROP_VISIBLE_COUNT,
164         PROP_BUILD_CONTENT,
165         PROP_NEED_PREVIEW
166 };
167
168 G_DEFINE_TYPE (CamelFolderSummary, camel_folder_summary, CAMEL_TYPE_OBJECT)
169
170 static gboolean
171 remove_each_item (gpointer uid,
172                   gpointer mi,
173                   gpointer user_data)
174 {
175         GSList **to_remove_infos = user_data;
176
177         *to_remove_infos = g_slist_prepend (*to_remove_infos, mi);
178
179         return TRUE;
180 }
181
182 static void
183 remove_all_loaded (CamelFolderSummary *summary)
184 {
185         GSList *to_remove_infos = NULL;
186
187         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
188
189         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
190
191         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
192         g_hash_table_foreach_remove (summary->priv->loaded_infos, remove_each_item, &to_remove_infos);
193         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
194
195         g_slist_foreach (to_remove_infos, (GFunc) camel_message_info_free, NULL);
196         g_slist_free (to_remove_infos);
197
198         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
199 }
200
201 static void
202 free_o_name (gpointer key,
203              gpointer value,
204              gpointer data)
205 {
206         g_object_unref (value);
207         g_free (key);
208 }
209
210 static void
211 folder_summary_dispose (GObject *object)
212 {
213         CamelFolderSummaryPrivate *priv;
214
215         priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (object);
216
217         if (priv->timeout_handle) {
218                 /* this should not happen, because the release timer
219                  * holds a reference on object */
220                 g_source_remove (priv->timeout_handle);
221                 priv->timeout_handle = 0;
222         }
223
224         if (priv->filter_index != NULL) {
225                 g_object_unref (priv->filter_index);
226                 priv->filter_index = NULL;
227         }
228
229         if (priv->filter_64 != NULL) {
230                 g_object_unref (priv->filter_64);
231                 priv->filter_64 = NULL;
232         }
233
234         if (priv->filter_qp != NULL) {
235                 g_object_unref (priv->filter_qp);
236                 priv->filter_qp = NULL;
237         }
238
239         if (priv->filter_uu != NULL) {
240                 g_object_unref (priv->filter_uu);
241                 priv->filter_uu = NULL;
242         }
243
244         if (priv->filter_save != NULL) {
245                 g_object_unref (priv->filter_save);
246                 priv->filter_save = NULL;
247         }
248
249         if (priv->filter_html != NULL) {
250                 g_object_unref (priv->filter_html);
251                 priv->filter_html = NULL;
252         }
253
254         if (priv->filter_stream != NULL) {
255                 g_object_unref (priv->filter_stream);
256                 priv->filter_stream = NULL;
257         }
258
259         if (priv->index != NULL) {
260                 g_object_unref (priv->index);
261                 priv->index = NULL;
262         }
263
264         if (priv->folder) {
265                 g_object_weak_unref (G_OBJECT (priv->folder), (GWeakNotify) g_nullify_pointer, &priv->folder);
266                 priv->folder = NULL;
267         }
268
269         /* Chain up to parent's dispose() method. */
270         G_OBJECT_CLASS (camel_folder_summary_parent_class)->dispose (object);
271 }
272
273 static void
274 folder_summary_finalize (GObject *object)
275 {
276         CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
277         CamelFolderSummaryPrivate *priv = summary->priv;
278
279         g_hash_table_destroy (priv->uids);
280         remove_all_loaded (summary);
281         g_hash_table_destroy (priv->loaded_infos);
282
283         g_hash_table_foreach (priv->filter_charset, free_o_name, NULL);
284         g_hash_table_destroy (priv->filter_charset);
285
286         g_hash_table_destroy (priv->preview_updates);
287
288         g_static_rec_mutex_free (&priv->summary_lock);
289         g_static_rec_mutex_free (&priv->io_lock);
290         g_static_rec_mutex_free (&priv->filter_lock);
291         g_static_rec_mutex_free (&priv->alloc_lock);
292         g_static_rec_mutex_free (&priv->ref_lock);
293
294         /* Chain up to parent's finalize() method. */
295         G_OBJECT_CLASS (camel_folder_summary_parent_class)->finalize (object);
296 }
297
298 static void
299 folder_summary_set_folder (CamelFolderSummary *summary,
300                            CamelFolder *folder)
301 {
302         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
303         g_return_if_fail (summary->priv->folder == NULL);
304         /* folder can be NULL in certain cases, see maildir-store */
305
306         summary->priv->folder = folder;
307         if (folder)
308                 g_object_weak_ref (G_OBJECT (folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
309 }
310
311 static void
312 folder_summary_set_property (GObject *object,
313                              guint property_id,
314                              const GValue *value,
315                              GParamSpec *pspec)
316 {
317         switch (property_id) {
318                 case PROP_FOLDER:
319                         folder_summary_set_folder (
320                                 CAMEL_FOLDER_SUMMARY (object),
321                                 CAMEL_FOLDER (g_value_get_object (value)));
322                         return;
323
324                 case PROP_BUILD_CONTENT:
325                         camel_folder_summary_set_build_content (
326                                 CAMEL_FOLDER_SUMMARY (object),
327                                 g_value_get_boolean (value));
328                         return;
329
330                 case PROP_NEED_PREVIEW:
331                         camel_folder_summary_set_need_preview (
332                                 CAMEL_FOLDER_SUMMARY (object),
333                                 g_value_get_boolean (value));
334                         return;
335         }
336
337         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
338 }
339
340 static void
341 folder_summary_get_property (GObject *object,
342                              guint property_id,
343                              GValue *value,
344                              GParamSpec *pspec)
345 {
346         switch (property_id) {
347                 case PROP_FOLDER:
348                         g_value_set_object (
349                                 value, camel_folder_summary_get_folder (
350                                 CAMEL_FOLDER_SUMMARY (object)));
351                         return;
352
353                 case PROP_SAVED_COUNT:
354                         g_value_set_uint (
355                                 value, camel_folder_summary_get_saved_count (
356                                 CAMEL_FOLDER_SUMMARY (object)));
357                         return;
358
359                 case PROP_UNREAD_COUNT:
360                         g_value_set_uint (
361                                 value, camel_folder_summary_get_unread_count (
362                                 CAMEL_FOLDER_SUMMARY (object)));
363                         return;
364
365                 case PROP_DELETED_COUNT:
366                         g_value_set_uint (
367                                 value, camel_folder_summary_get_deleted_count (
368                                 CAMEL_FOLDER_SUMMARY (object)));
369                         return;
370
371                 case PROP_JUNK_COUNT:
372                         g_value_set_uint (
373                                 value, camel_folder_summary_get_junk_count (
374                                 CAMEL_FOLDER_SUMMARY (object)));
375                         return;
376
377                 case PROP_JUNK_NOT_DELETED_COUNT:
378                         g_value_set_uint (
379                                 value, camel_folder_summary_get_junk_not_deleted_count (
380                                 CAMEL_FOLDER_SUMMARY (object)));
381                         return;
382
383                 case PROP_VISIBLE_COUNT:
384                         g_value_set_uint (
385                                 value, camel_folder_summary_get_visible_count (
386                                 CAMEL_FOLDER_SUMMARY (object)));
387                         return;
388
389                 case PROP_BUILD_CONTENT:
390                         g_value_set_boolean (
391                                 value, camel_folder_summary_get_build_content (
392                                 CAMEL_FOLDER_SUMMARY (object)));
393                         return;
394
395                 case PROP_NEED_PREVIEW:
396                         g_value_set_boolean (
397                                 value, camel_folder_summary_get_need_preview (
398                                 CAMEL_FOLDER_SUMMARY (object)));
399                         return;
400         }
401
402         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
403 }
404
405 static gboolean
406 is_in_memory_summary (CamelFolderSummary *summary)
407 {
408         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
409
410         return (summary->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0;
411 }
412
413 #define UPDATE_COUNTS_ADD               (1)
414 #define UPDATE_COUNTS_SUB               (2)
415 #define UPDATE_COUNTS_ADD_WITHOUT_TOTAL (3)
416 #define UPDATE_COUNTS_SUB_WITHOUT_TOTAL (4)
417
418 static gboolean
419 folder_summary_update_counts_by_flags (CamelFolderSummary *summary,
420                                        guint32 flags,
421                                        gint op_type)
422 {
423         gint unread = 0, deleted = 0, junk = 0;
424         gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
425         gboolean subtract = op_type == UPDATE_COUNTS_SUB || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
426         gboolean without_total = op_type == UPDATE_COUNTS_ADD_WITHOUT_TOTAL || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
427         gboolean changed = FALSE;
428         GObject *summary_object;
429
430         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
431
432         summary_object = G_OBJECT (summary);
433
434         if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
435                 CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
436
437                 is_junk_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
438                 is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
439         }
440
441         if (!(flags & CAMEL_MESSAGE_SEEN))
442                 unread = subtract ? -1 : 1;
443
444         if (flags & CAMEL_MESSAGE_DELETED)
445                 deleted = subtract ? -1 : 1;
446
447         if (flags & CAMEL_MESSAGE_JUNK)
448                 junk = subtract ? -1 : 1;
449
450         dd (printf ("%p: %d %d %d | %d %d %d \n", (gpointer) summary, unread, deleted, junk, summary->priv->unread_count, summary->priv->visible_count, summary->priv->saved_count));
451
452         g_object_freeze_notify (summary_object);
453
454         if (deleted) {
455                 summary->priv->deleted_count += deleted;
456                 g_object_notify (summary_object, "deleted-count");
457                 changed = TRUE;
458         }
459
460         if (junk) {
461                 summary->priv->junk_count += junk;
462                 g_object_notify (summary_object, "junk-count");
463                 changed = TRUE;
464         }
465
466         if (junk && !deleted) {
467                 summary->priv->junk_not_deleted_count += junk;
468                 g_object_notify (summary_object, "junk-not-deleted-count");
469                 changed = TRUE;
470         }
471
472         if (!junk && !deleted) {
473                 summary->priv->visible_count += subtract ? -1 : 1;
474                 g_object_notify (summary_object, "visible-count");
475                 changed = TRUE;
476         }
477
478         if (junk && !is_junk_folder)
479                 unread = 0;
480         if (deleted && !is_trash_folder)
481                 unread = 0;
482
483         if (unread) {
484                 summary->priv->unread_count += unread;
485                 g_object_notify (summary_object, "unread-count");
486                 changed = TRUE;
487         }
488
489         if (!without_total) {
490                 summary->priv->saved_count += subtract ? -1 : 1;
491                 g_object_notify (summary_object, "saved-count");
492                 changed = TRUE;
493         }
494
495         if (changed)
496                 camel_folder_summary_touch (summary);
497
498         g_object_thaw_notify (summary_object);
499
500         dd (printf ("%p: %d %d %d | %d %d %d\n", (gpointer) summary, unread, deleted, junk, summary->priv->unread_count, summary->priv->visible_count, summary->priv->saved_count));
501
502         return changed;
503 }
504
505 static gboolean
506 summary_header_from_db (CamelFolderSummary *summary,
507                         CamelFIRecord *record)
508 {
509         io (printf ("Loading header from db \n"));
510
511         summary->version = record->version;
512
513         /* We may not worry, as we are setting a new standard here */
514 #if 0
515         /* Legacy version check, before version 12 we have no upgrade knowledge */
516         if ((summary->version > 0xff) && (summary->version & 0xff) < 12) {
517                 io (printf ("Summary header version mismatch"));
518                 errno = EINVAL;
519                 return FALSE;
520         }
521
522         if (!(summary->version < 0x100 && summary->version >= 13))
523                 io (printf ("Loading legacy summary\n"));
524         else
525                 io (printf ("loading new-format summary\n"));
526 #endif
527
528         summary->flags = record->flags;
529         summary->priv->nextuid = record->nextuid;
530         summary->time = record->time;
531         summary->priv->saved_count = record->saved_count;
532
533         summary->priv->unread_count = record->unread_count;
534         summary->priv->deleted_count = record->deleted_count;
535         summary->priv->junk_count = record->junk_count;
536         summary->priv->visible_count = record->visible_count;
537         summary->priv->junk_not_deleted_count = record->jnd_count;
538
539         return TRUE;
540 }
541
542 static  CamelFIRecord *
543 summary_header_to_db (CamelFolderSummary *summary,
544                       GError **error)
545 {
546         CamelFIRecord * record = g_new0 (CamelFIRecord, 1);
547         CamelStore *parent_store;
548         CamelDB *db;
549         const gchar *table_name;
550
551         /* Though we are going to read, we do this during write,
552          * so lets use it that way. */
553         table_name = camel_folder_get_full_name (summary->priv->folder);
554         parent_store = camel_folder_get_parent_store (summary->priv->folder);
555         db = parent_store->cdb_w;
556
557         io (printf ("Savining header to db\n"));
558
559         record->folder_name = g_strdup (table_name);
560
561         /* we always write out the current version */
562         record->version = CAMEL_FOLDER_SUMMARY_VERSION;
563         record->flags  = summary->flags;
564         record->nextuid = summary->priv->nextuid;
565         record->time = summary->time;
566
567         if (!is_in_memory_summary (summary)) {
568                 /* FIXME: Ever heard of Constructors and initializing ? */
569                 if (camel_db_count_total_message_info (db, table_name, &(record->saved_count), NULL))
570                         record->saved_count = 0;
571                 if (camel_db_count_junk_message_info (db, table_name, &(record->junk_count), NULL))
572                         record->junk_count = 0;
573                 if (camel_db_count_deleted_message_info (db, table_name, &(record->deleted_count), NULL))
574                         record->deleted_count = 0;
575                 if (camel_db_count_unread_message_info (db, table_name, &(record->unread_count), NULL))
576                         record->unread_count = 0;
577                 if (camel_db_count_visible_message_info (db, table_name, &(record->visible_count), NULL))
578                         record->visible_count = 0;
579                 if (camel_db_count_junk_not_deleted_message_info (db, table_name, &(record->jnd_count), NULL))
580                         record->jnd_count = 0;
581         }
582
583         summary->priv->unread_count = record->unread_count;
584         summary->priv->deleted_count = record->deleted_count;
585         summary->priv->junk_count = record->junk_count;
586         summary->priv->visible_count = record->visible_count;
587         summary->priv->junk_not_deleted_count = record->jnd_count;
588
589         return record;
590 }
591
592 static CamelMessageInfo *
593 message_info_from_db (CamelFolderSummary *summary,
594                       CamelMIRecord *record)
595 {
596         CamelMessageInfoBase *mi;
597         gint i;
598         gint count;
599         gchar *part, *label;
600
601         mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
602
603         io (printf ("Loading message info from db\n"));
604
605         mi->flags = record->flags;
606         mi->size = record->size;
607         mi->date_sent = record->dsent;
608         mi->date_received = record->dreceived;
609
610         mi->uid = (gchar *) camel_pstring_strdup (record->uid);
611         mi->subject = (gchar *) camel_pstring_add (record->subject, FALSE);
612         mi->from = (gchar *) camel_pstring_add (record->from, FALSE);
613         mi->to = (gchar *) camel_pstring_add (record->to, FALSE);
614         mi->cc = (gchar *) camel_pstring_add (record->cc, FALSE);
615         mi->mlist = (gchar *) camel_pstring_add (record->mlist, FALSE);
616
617         /* Evolution itself doesn't yet use this, so we ignore it (saving some memory) */
618         mi->bodystructure = NULL;
619
620         /* Extract Message id & References */
621         mi->content = NULL;
622         part = record->part;
623         if (part) {
624                 mi->message_id.id.part.hi = bdata_extract_digit (&part);
625                 mi->message_id.id.part.lo = bdata_extract_digit (&part);
626                 count = bdata_extract_digit (&part);
627
628                 if (count > 0) {
629                         mi->references = g_malloc (sizeof (*mi->references) + ((count - 1) * sizeof (mi->references->references[0])));
630                         mi->references->size = count;
631                         for (i = 0; i < count; i++) {
632                                 mi->references->references[i].id.part.hi = bdata_extract_digit (&part);
633                                 mi->references->references[i].id.part.lo = bdata_extract_digit (&part);
634                         }
635                 } else
636                         mi->references = NULL;
637
638         }
639
640         /* Extract User flags/labels */
641         part = record->labels;
642         if (part) {
643                 label = part;
644                 for (i = 0; part[i]; i++) {
645
646                         if (part[i] == ' ') {
647                                 part[i] = 0;
648                                 camel_flag_set (&mi->user_flags, label, TRUE);
649                                 label = &(part[i + 1]);
650                         }
651                 }
652                 camel_flag_set (&mi->user_flags, label, TRUE);
653         }
654
655         /* Extract User tags */
656         part = record->usertags;
657         count = bdata_extract_digit (&part);
658         for (i = 0; i < count; i++) {
659                 gchar *name, *value;
660
661                 name = bdata_extract_string (&part);
662                 value = bdata_extract_string (&part);
663                 camel_tag_set (&mi->user_tags, name, value);
664
665                 g_free (name);
666                 g_free (value);
667         }
668
669         return (CamelMessageInfo *) mi;
670 }
671
672 static CamelMIRecord *
673 message_info_to_db (CamelFolderSummary *summary,
674                     CamelMessageInfo *info)
675 {
676         CamelMIRecord *record = g_new0 (CamelMIRecord, 1);
677         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
678         GString *tmp;
679         CamelFlag *flag;
680         CamelTag *tag;
681         gint count, i;
682
683         /* Assume that we dont have to take care of DB Safeness. It will be done while doing the DB transaction */
684         record->uid = (gchar *) camel_pstring_strdup (camel_message_info_uid (mi));
685         record->flags = mi->flags;
686
687         record->read =  ((mi->flags & (CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_JUNK))) ? 1 : 0;
688         record->deleted = mi->flags & CAMEL_MESSAGE_DELETED ? 1 : 0;
689         record->replied = mi->flags & CAMEL_MESSAGE_ANSWERED ? 1 : 0;
690         record->important = mi->flags & CAMEL_MESSAGE_FLAGGED ? 1 : 0;
691         record->junk = mi->flags & CAMEL_MESSAGE_JUNK ? 1 : 0;
692         record->dirty = mi->flags & CAMEL_MESSAGE_FOLDER_FLAGGED ? 1 : 0;
693         record->attachment = mi->flags & CAMEL_MESSAGE_ATTACHMENTS ? 1 : 0;
694
695         record->size = mi->size;
696         record->dsent = mi->date_sent;
697         record->dreceived = mi->date_received;
698
699         record->subject = (gchar *) camel_pstring_strdup (camel_message_info_subject (mi));
700         record->from = (gchar *) camel_pstring_strdup (camel_message_info_from (mi));
701         record->to = (gchar *) camel_pstring_strdup (camel_message_info_to (mi));
702         record->cc = (gchar *) camel_pstring_strdup (camel_message_info_cc (mi));
703         record->mlist = (gchar *) camel_pstring_strdup (camel_message_info_mlist (mi));
704
705         record->followup_flag = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "follow-up"));
706         record->followup_completed_on = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "completed-on"));
707         record->followup_due_by = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "due-by"));
708
709         record->bodystructure = mi->bodystructure ? g_strdup (mi->bodystructure) : NULL;
710
711         tmp = g_string_new (NULL);
712         if (mi->references) {
713                 g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) mi->references->size);
714                 for (i = 0; i < mi->references->size; i++)
715                         g_string_append_printf (tmp, " %lu %lu", (gulong) mi->references->references[i].id.part.hi, (gulong) mi->references->references[i].id.part.lo);
716         } else {
717                 g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) 0);
718         }
719         record->part = tmp->str;
720         g_string_free (tmp, FALSE);
721
722         tmp = g_string_new (NULL);
723         flag = mi->user_flags;
724         while (flag) {
725                 g_string_append_printf (tmp, "%s ", flag->name);
726                 flag = flag->next;
727         }
728
729         /* Strip off the last space */
730         if (tmp->len)
731                 tmp->len--;
732
733         record->labels = tmp->str;
734         g_string_free (tmp, FALSE);
735
736         tmp = g_string_new (NULL);
737         count = camel_tag_list_size (&mi->user_tags);
738         g_string_append_printf (tmp, "%lu", (gulong) count);
739         tag = mi->user_tags;
740         while (tag) {
741                 /* FIXME: Should we handle empty tags? Can it be empty? If it potential crasher ahead*/
742                 g_string_append_printf (tmp, " %lu-%s %lu-%s", (gulong) strlen (tag->name), tag->name, (gulong) strlen (tag->value), tag->value);
743                 tag = tag->next;
744         }
745         record->usertags = tmp->str;
746         g_string_free (tmp, FALSE);
747
748         return record;
749 }
750
751 static CamelMessageContentInfo *
752 content_info_from_db (CamelFolderSummary *summary,
753                       CamelMIRecord *record)
754 {
755         CamelMessageContentInfo *ci;
756         gchar *type, *subtype;
757         guint32 count, i;
758         CamelContentType *ct;
759         gchar *part = record->cinfo;
760
761         io (printf ("Loading content info from db\n"));
762
763         if (!part)
764                 return NULL;
765
766         ci = camel_folder_summary_content_info_new (summary);
767         if (*part == ' ') part++; /* Move off the space in the record */
768
769         type = bdata_extract_string (&part);
770         subtype = bdata_extract_string (&part);
771         ct = camel_content_type_new (type, subtype);
772         g_free (type);          /* can this be removed? */
773         g_free (subtype);
774         count = bdata_extract_digit (&part);
775
776         for (i = 0; i < count; i++) {
777                 gchar *name, *value;
778                 name = bdata_extract_string (&part);
779                 value = bdata_extract_string (&part);
780
781                 camel_content_type_set_param (ct, name, value);
782                 /* TODO: do this so we dont have to double alloc/free */
783                 g_free (name);
784                 g_free (value);
785         }
786         ci->type = ct;
787
788         /* FIXME[disk-summary] move all these to camel pstring */
789         ci->id = bdata_extract_string (&part);
790         ci->description = bdata_extract_string (&part);
791         ci->encoding = bdata_extract_string (&part);
792         ci->size = bdata_extract_digit (&part);
793
794         record->cinfo = part; /* Keep moving the cursor in the record */
795
796         ci->childs = NULL;
797
798         return ci;
799 }
800
801 static gboolean
802 content_info_to_db (CamelFolderSummary *summary,
803                     CamelMessageContentInfo *ci,
804                     CamelMIRecord *record)
805 {
806         CamelContentType *ct;
807         struct _camel_header_param *hp;
808         GString *str = g_string_new (NULL);
809         gchar *oldr;
810
811         io (printf ("Saving content info to db\n"));
812
813         ct = ci->type;
814         if (ct) {
815                 if (ct->type)
816                         g_string_append_printf (str, " %d-%s", (gint) strlen (ct->type), ct->type);
817                 else
818                         g_string_append_printf (str, " 0-");
819                 if (ct->subtype)
820                         g_string_append_printf (str, " %d-%s", (gint) strlen (ct->subtype), ct->subtype);
821                 else
822                         g_string_append_printf (str, " 0-");
823                 g_string_append_printf (str, " %d", my_list_size ((struct _node **) &ct->params));
824                 hp = ct->params;
825                 while (hp) {
826                         if (hp->name)
827                                 g_string_append_printf (str, " %d-%s", (gint) strlen (hp->name), hp->name);
828                         else
829                                 g_string_append_printf (str, " 0-");
830                         if (hp->value)
831                                 g_string_append_printf (str, " %d-%s", (gint) strlen (hp->value), hp->value);
832                         else
833                                 g_string_append_printf (str, " 0-");
834                         hp = hp->next;
835                 }
836         } else {
837                 g_string_append_printf (str, " %d-", 0);
838                 g_string_append_printf (str, " %d-", 0);
839                 g_string_append_printf (str, " %d", 0);
840         }
841
842         if (ci->id)
843                 g_string_append_printf (str, " %d-%s", (gint) strlen (ci->id), ci->id);
844         else
845                 g_string_append_printf (str, " 0-");
846         if (ci->description)
847                 g_string_append_printf (str, " %d-%s", (gint) strlen (ci->description), ci->description);
848         else
849                 g_string_append_printf (str, " 0-");
850         if (ci->encoding)
851                 g_string_append_printf (str, " %d-%s", (gint) strlen (ci->encoding), ci->encoding);
852         else
853                 g_string_append_printf (str, " 0-");
854         g_string_append_printf (str, " %u", ci->size);
855
856         if (record->cinfo) {
857                 oldr = record->cinfo;
858                 record->cinfo = g_strconcat (oldr, str->str, NULL);
859                 g_free (oldr); g_string_free (str, TRUE);
860         } else {
861                 record->cinfo = str->str;
862                 g_string_free (str, FALSE);
863         }
864
865         return TRUE;
866 }
867
868 /**
869  * camel_folder_summary_replace_flags:
870  * @summary: a #CamelFolderSummary
871  * @info: a #CamelMessageInfo
872  *
873  * Updates internal counts based on the flags in @info.
874  *
875  * Returns: Whether any count changed
876  *
877  * Since: 3.6
878  **/
879 gboolean
880 camel_folder_summary_replace_flags (CamelFolderSummary *summary,
881                                     CamelMessageInfo *info)
882 {
883         guint32 old_flags, new_flags, added_flags, removed_flags;
884         gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
885         GObject *summary_object;
886         gboolean changed = FALSE;
887
888         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
889         g_return_val_if_fail (info != NULL, FALSE);
890
891         if (!camel_message_info_uid (info) ||
892             !camel_folder_summary_check_uid (summary, camel_message_info_uid (info)))
893                 return FALSE;
894
895         summary_object = G_OBJECT (summary);
896
897         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
898         g_object_freeze_notify (summary_object);
899
900         old_flags = GPOINTER_TO_UINT (g_hash_table_lookup (summary->priv->uids, camel_message_info_uid (info)));
901         new_flags = camel_message_info_flags (info);
902
903         if ((old_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED) == (new_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED)) {
904                 g_object_thaw_notify (summary_object);
905                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
906                 return FALSE;
907         }
908
909         if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
910                 CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
911
912                 is_junk_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
913                 is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
914         }
915
916         added_flags = new_flags & (~(old_flags & new_flags));
917         removed_flags = old_flags & (~(old_flags & new_flags));
918
919         if ((old_flags & CAMEL_MESSAGE_SEEN) == (new_flags & CAMEL_MESSAGE_SEEN)) {
920                 /* unread count is different from others, it asks for nonexistence
921                  * of the flag, thus if it wasn't changed, then simply set it
922                  * in added/removed, thus there are no false notifications
923                  * on unread counts */
924                 added_flags |= CAMEL_MESSAGE_SEEN;
925                 removed_flags |= CAMEL_MESSAGE_SEEN;
926         } else if ((!is_junk_folder && (new_flags & CAMEL_MESSAGE_JUNK) != 0 &&
927                    (old_flags & CAMEL_MESSAGE_JUNK) == (new_flags & CAMEL_MESSAGE_JUNK)) ||
928                    (!is_trash_folder && (new_flags & CAMEL_MESSAGE_DELETED) != 0 &&
929                    (old_flags & CAMEL_MESSAGE_DELETED) == (new_flags & CAMEL_MESSAGE_DELETED))) {
930                 /* The message was set read or unread, but it is a junk or deleted message,
931                  * in a non-Junk/non-Trash folder, thus it doesn't influence an unread count
932                  * there, thus pretend unread didn't change */
933                 added_flags |= CAMEL_MESSAGE_SEEN;
934                 removed_flags |= CAMEL_MESSAGE_SEEN;
935         }
936
937         /* decrement counts with removed flags */
938         changed = folder_summary_update_counts_by_flags (summary, removed_flags, UPDATE_COUNTS_SUB_WITHOUT_TOTAL) || changed;
939         /* increment counts with added flags */
940         changed = folder_summary_update_counts_by_flags (summary, added_flags, UPDATE_COUNTS_ADD_WITHOUT_TOTAL) || changed;
941
942         /* update current flags on the summary */
943         g_hash_table_insert (summary->priv->uids,
944                 (gpointer) camel_pstring_strdup (camel_message_info_uid (info)),
945                 GUINT_TO_POINTER (new_flags));
946
947         g_object_thaw_notify (summary_object);
948         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
949
950         return changed;
951 }
952
953 static CamelMessageInfo *
954 message_info_clone (CamelFolderSummary *summary,
955                     const CamelMessageInfo *mi)
956 {
957         CamelMessageInfoBase *to, *from = (CamelMessageInfoBase *) mi;
958         CamelFlag *flag;
959         CamelTag *tag;
960
961         to = (CamelMessageInfoBase *) camel_message_info_new (summary);
962
963         to->flags = from->flags;
964         to->size = from->size;
965         to->date_sent = from->date_sent;
966         to->date_received = from->date_received;
967         to->refcount = 1;
968
969         /* NB: We don't clone the uid */
970
971         to->subject = camel_pstring_strdup (from->subject);
972         to->from = camel_pstring_strdup (from->from);
973         to->to = camel_pstring_strdup (from->to);
974         to->cc = camel_pstring_strdup (from->cc);
975         to->mlist = camel_pstring_strdup (from->mlist);
976         memcpy (&to->message_id, &from->message_id, sizeof (to->message_id));
977         to->preview = g_strdup (from->preview);
978         if (from->references) {
979                 gint len = sizeof (*from->references) + ((from->references->size - 1) * sizeof (from->references->references[0]));
980
981                 to->references = g_malloc (len);
982                 memcpy (to->references, from->references, len);
983         }
984
985         flag = from->user_flags;
986         while (flag) {
987                 camel_flag_set (&to->user_flags, flag->name, TRUE);
988                 flag = flag->next;
989         }
990
991         tag = from->user_tags;
992         while (tag) {
993                 camel_tag_set (&to->user_tags, tag->name, tag->value);
994                 tag = tag->next;
995         }
996
997         if (from->content) {
998                 /* FIXME: copy content-infos */
999         }
1000
1001         return (CamelMessageInfo *) to;
1002 }
1003
1004 static gconstpointer
1005 info_ptr (const CamelMessageInfo *mi,
1006           gint id)
1007 {
1008         switch (id) {
1009                 case CAMEL_MESSAGE_INFO_SUBJECT:
1010                         return ((const CamelMessageInfoBase *) mi)->subject;
1011                 case CAMEL_MESSAGE_INFO_FROM:
1012                         return ((const CamelMessageInfoBase *) mi)->from;
1013                 case CAMEL_MESSAGE_INFO_TO:
1014                         return ((const CamelMessageInfoBase *) mi)->to;
1015                 case CAMEL_MESSAGE_INFO_CC:
1016                         return ((const CamelMessageInfoBase *) mi)->cc;
1017                 case CAMEL_MESSAGE_INFO_MLIST:
1018                         return ((const CamelMessageInfoBase *) mi)->mlist;
1019                 case CAMEL_MESSAGE_INFO_MESSAGE_ID:
1020                         return &((const CamelMessageInfoBase *) mi)->message_id;
1021                 case CAMEL_MESSAGE_INFO_REFERENCES:
1022                         return ((const CamelMessageInfoBase *) mi)->references;
1023                 case CAMEL_MESSAGE_INFO_USER_FLAGS:
1024                         return ((const CamelMessageInfoBase *) mi)->user_flags;
1025                 case CAMEL_MESSAGE_INFO_USER_TAGS:
1026                         return ((const CamelMessageInfoBase *) mi)->user_tags;
1027                 case CAMEL_MESSAGE_INFO_HEADERS:
1028                         return ((const CamelMessageInfoBase *) mi)->headers;
1029                 case CAMEL_MESSAGE_INFO_CONTENT:
1030                         return ((const CamelMessageInfoBase *) mi)->content;
1031                 case CAMEL_MESSAGE_INFO_PREVIEW:
1032                         return ((const CamelMessageInfoBase *) mi)->preview;
1033                 default:
1034                         g_return_val_if_reached (NULL);
1035         }
1036 }
1037
1038 static guint32
1039 info_uint32 (const CamelMessageInfo *mi,
1040              gint id)
1041 {
1042         switch (id) {
1043                 case CAMEL_MESSAGE_INFO_FLAGS:
1044                         return ((const CamelMessageInfoBase *) mi)->flags;
1045                 case CAMEL_MESSAGE_INFO_SIZE:
1046                         return ((const CamelMessageInfoBase *) mi)->size;
1047                 default:
1048                         g_return_val_if_reached (0);
1049         }
1050 }
1051
1052 static time_t
1053 info_time (const CamelMessageInfo *mi,
1054            gint id)
1055 {
1056         switch (id) {
1057                 case CAMEL_MESSAGE_INFO_DATE_SENT:
1058                         return ((const CamelMessageInfoBase *) mi)->date_sent;
1059                 case CAMEL_MESSAGE_INFO_DATE_RECEIVED:
1060                         return ((const CamelMessageInfoBase *) mi)->date_received;
1061                 default:
1062                         g_return_val_if_reached (0);
1063         }
1064 }
1065
1066 static gboolean
1067 info_user_flag (const CamelMessageInfo *mi,
1068                 const gchar *id)
1069 {
1070         return camel_flag_get (&((CamelMessageInfoBase *) mi)->user_flags, id);
1071 }
1072
1073 static const gchar *
1074 info_user_tag (const CamelMessageInfo *mi,
1075                const gchar *id)
1076 {
1077         return camel_tag_get (&((CamelMessageInfoBase *) mi)->user_tags, id);
1078 }
1079
1080 static gboolean
1081 info_set_user_flag (CamelMessageInfo *info,
1082                     const gchar *name,
1083                     gboolean value)
1084 {
1085         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
1086         gint res;
1087
1088         res = camel_flag_set (&mi->user_flags, name, value);
1089
1090         if (mi->summary && res && mi->summary->priv->folder && mi->uid
1091             && camel_folder_summary_check_uid (mi->summary, mi->uid)) {
1092                 CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
1093
1094                 mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
1095                 mi->dirty = TRUE;
1096                 camel_folder_summary_touch (mi->summary);
1097                 camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
1098                 camel_folder_changed (mi->summary->priv->folder, changes);
1099                 camel_folder_change_info_free (changes);
1100         }
1101
1102         return res;
1103 }
1104
1105 static gboolean
1106 info_set_user_tag (CamelMessageInfo *info,
1107                    const gchar *name,
1108                    const gchar *value)
1109 {
1110         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
1111         gint res;
1112
1113         res = camel_tag_set (&mi->user_tags, name, value);
1114
1115         if (mi->summary && res && mi->summary->priv->folder && mi->uid
1116             && camel_folder_summary_check_uid (mi->summary, mi->uid)) {
1117                 CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
1118
1119                 mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
1120                 mi->dirty = TRUE;
1121                 camel_folder_summary_touch (mi->summary);
1122                 camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
1123                 camel_folder_changed (mi->summary->priv->folder, changes);
1124                 camel_folder_change_info_free (changes);
1125         }
1126
1127         return res;
1128 }
1129
1130 static gboolean
1131 info_set_flags (CamelMessageInfo *info,
1132                 guint32 flags,
1133                 guint32 set)
1134 {
1135         guint32 old;
1136         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
1137         gboolean counts_changed = FALSE;
1138
1139         old = camel_message_info_flags (info);
1140         mi->flags = (old & ~flags) | (set & flags);
1141         if (old != mi->flags) {
1142                 mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
1143                 mi->dirty = TRUE;
1144                 if (mi->summary)
1145                         camel_folder_summary_touch (mi->summary);
1146         }
1147
1148         if (mi->summary) {
1149                 camel_folder_summary_lock (mi->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1150                 g_object_freeze_notify (G_OBJECT (mi->summary));
1151                 counts_changed = camel_folder_summary_replace_flags (mi->summary, info);
1152         }
1153
1154         if (!counts_changed && ((old & ~CAMEL_MESSAGE_SYSTEM_MASK) == (mi->flags & ~CAMEL_MESSAGE_SYSTEM_MASK)) && !((set & CAMEL_MESSAGE_JUNK_LEARN) && !(set & CAMEL_MESSAGE_JUNK))) {
1155                 if (mi->summary) {
1156                         g_object_thaw_notify (G_OBJECT (mi->summary));
1157                         camel_folder_summary_unlock (mi->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1158                 }
1159                 return FALSE;
1160         }
1161
1162         if (mi->summary) {
1163                 g_object_thaw_notify (G_OBJECT (mi->summary));
1164                 camel_folder_summary_unlock (mi->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1165         }
1166
1167         if (mi->summary && mi->summary->priv->folder && mi->uid) {
1168                 CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
1169
1170                 camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
1171                 camel_folder_changed (mi->summary->priv->folder, changes);
1172                 camel_folder_change_info_free (changes);
1173         }
1174
1175         return TRUE;
1176 }
1177
1178 static void
1179 camel_folder_summary_class_init (CamelFolderSummaryClass *class)
1180 {
1181         GObjectClass *object_class;
1182
1183         g_type_class_add_private (class, sizeof (CamelFolderSummaryPrivate));
1184
1185         object_class = G_OBJECT_CLASS (class);
1186         object_class->set_property = folder_summary_set_property;
1187         object_class->get_property = folder_summary_get_property;
1188         object_class->dispose = folder_summary_dispose;
1189         object_class->finalize = folder_summary_finalize;
1190
1191         class->message_info_size = sizeof (CamelMessageInfoBase);
1192         class->content_info_size = sizeof (CamelMessageContentInfo);
1193
1194         class->summary_header_from_db = summary_header_from_db;
1195         class->summary_header_to_db = summary_header_to_db;
1196         class->message_info_from_db = message_info_from_db;
1197         class->message_info_to_db = message_info_to_db;
1198         class->content_info_from_db = content_info_from_db;
1199         class->content_info_to_db = content_info_to_db;
1200
1201         class->message_info_new_from_header  = message_info_new_from_header;
1202         class->message_info_new_from_parser = message_info_new_from_parser;
1203         class->message_info_new_from_message = message_info_new_from_message;
1204         class->message_info_free = message_info_free;
1205         class->message_info_clone = message_info_clone;
1206         class->message_info_from_uid = message_info_from_uid;
1207
1208         class->content_info_new_from_header  = content_info_new_from_header;
1209         class->content_info_new_from_parser = content_info_new_from_parser;
1210         class->content_info_new_from_message = content_info_new_from_message;
1211         class->content_info_free = content_info_free;
1212
1213         class->next_uid_string = next_uid_string;
1214
1215         class->info_ptr = info_ptr;
1216         class->info_uint32 = info_uint32;
1217         class->info_time = info_time;
1218         class->info_user_flag = info_user_flag;
1219         class->info_user_tag = info_user_tag;
1220
1221         class->info_set_user_flag = info_set_user_flag;
1222         class->info_set_user_tag = info_set_user_tag;
1223
1224         class->info_set_flags = info_set_flags;
1225
1226         /**
1227          * CamelFolderSummary:folder
1228          *
1229          * The #CamelFolder to which the folder summary belongs.
1230          **/
1231         g_object_class_install_property (
1232                 object_class,
1233                 PROP_FOLDER,
1234                 g_param_spec_object (
1235                         "folder",
1236                         "Folder",
1237                         "The folder to which the folder summary belongs",
1238                         CAMEL_TYPE_FOLDER,
1239                         G_PARAM_READWRITE |
1240                         G_PARAM_CONSTRUCT_ONLY));
1241
1242         /**
1243          * CamelFolderSummary:saved-count
1244          *
1245          * How many infos is saved in a summary.
1246          **/
1247         g_object_class_install_property (
1248                 object_class,
1249                 PROP_SAVED_COUNT,
1250                 g_param_spec_uint (
1251                         "saved-count",
1252                         "Saved count",
1253                         "How many infos is savef in a summary",
1254                         0,  G_MAXUINT32,
1255                         0, G_PARAM_READABLE));
1256
1257         /**
1258          * CamelFolderSummary:unread-count
1259          *
1260          * How many unread infos is saved in a summary.
1261          **/
1262         g_object_class_install_property (
1263                 object_class,
1264                 PROP_UNREAD_COUNT,
1265                 g_param_spec_uint (
1266                         "unread-count",
1267                         "Unread count",
1268                         "How many unread infos is saved in a summary",
1269                         0,  G_MAXUINT32,
1270                         0, G_PARAM_READABLE));
1271
1272         /**
1273          * CamelFolderSummary:deleted-count
1274          *
1275          * How many deleted infos is saved in a summary.
1276          **/
1277         g_object_class_install_property (
1278                 object_class,
1279                 PROP_DELETED_COUNT,
1280                 g_param_spec_uint (
1281                         "deleted-count",
1282                         "Deleted count",
1283                         "How many deleted infos is saved in a summary",
1284                         0,  G_MAXUINT32,
1285                         0, G_PARAM_READABLE));
1286
1287         /**
1288          * CamelFolderSummary:junk-count
1289          *
1290          * How many junk infos is saved in a summary.
1291          **/
1292         g_object_class_install_property (
1293                 object_class,
1294                 PROP_JUNK_COUNT,
1295                 g_param_spec_uint (
1296                         "junk-count",
1297                         "Junk count",
1298                         "How many junk infos is saved in a summary",
1299                         0,  G_MAXUINT32,
1300                         0, G_PARAM_READABLE));
1301
1302         /**
1303          * CamelFolderSummary:junk-not-deleted-count
1304          *
1305          * How many junk and not deleted infos is saved in a summary.
1306          **/
1307         g_object_class_install_property (
1308                 object_class,
1309                 PROP_JUNK_NOT_DELETED_COUNT,
1310                 g_param_spec_uint (
1311                         "junk-not-deleted-count",
1312                         "Junk not deleted count",
1313                         "How many junk and not deleted infos is saved in a summary",
1314                         0,  G_MAXUINT32,
1315                         0, G_PARAM_READABLE));
1316
1317         /**
1318          * CamelFolderSummary:visible-count
1319          *
1320          * How many visible (not deleted and not junk) infos is saved in a summary.
1321          **/
1322         g_object_class_install_property (
1323                 object_class,
1324                 PROP_VISIBLE_COUNT,
1325                 g_param_spec_uint (
1326                         "visible-count",
1327                         "Visible count",
1328                         "How many visible (not deleted and not junk) infos is saved in a summary",
1329                         0,  G_MAXUINT32,
1330                         0, G_PARAM_READABLE));
1331
1332         /**
1333          * CamelFolderSummary:build-content
1334          *
1335          * Whether to build CamelMessageInfo.content.
1336          **/
1337         g_object_class_install_property (
1338                 object_class,
1339                 PROP_BUILD_CONTENT,
1340                 g_param_spec_boolean (
1341                         "build-content",
1342                         "Build content",
1343                         "Whether to build CamelMessageInfo.content",
1344                         FALSE,
1345                         G_PARAM_READWRITE));
1346
1347         /**
1348          * CamelFolderSummary:need-preview
1349          *
1350          **/
1351         g_object_class_install_property (
1352                 object_class,
1353                 PROP_NEED_PREVIEW,
1354                 g_param_spec_boolean (
1355                         "need-preview",
1356                         "Need preview",
1357                         "",
1358                         FALSE,
1359                         G_PARAM_READWRITE));
1360 }
1361
1362 static void
1363 camel_folder_summary_init (CamelFolderSummary *summary)
1364 {
1365         summary->priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (summary);
1366
1367         summary->version = CAMEL_FOLDER_SUMMARY_VERSION;
1368         summary->flags = 0;
1369         summary->time = 0;
1370
1371         summary->priv->filter_charset = g_hash_table_new (
1372                 camel_strcase_hash, camel_strcase_equal);
1373
1374         summary->priv->need_preview = FALSE;
1375         summary->priv->preview_updates = g_hash_table_new (g_str_hash, g_str_equal);
1376
1377         summary->priv->nextuid = 1;
1378         summary->priv->uids = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
1379         summary->priv->loaded_infos = g_hash_table_new (g_str_hash, g_str_equal);
1380
1381         g_static_rec_mutex_init (&summary->priv->summary_lock);
1382         g_static_rec_mutex_init (&summary->priv->io_lock);
1383         g_static_rec_mutex_init (&summary->priv->filter_lock);
1384         g_static_rec_mutex_init (&summary->priv->alloc_lock);
1385         g_static_rec_mutex_init (&summary->priv->ref_lock);
1386
1387         summary->priv->cache_load_time = 0;
1388         summary->priv->timeout_handle = 0;
1389 }
1390
1391 /**
1392  * camel_folder_summary_new:
1393  * @folder: parent #CamelFolder object
1394  *
1395  * Create a new #CamelFolderSummary object.
1396  *
1397  * Returns: a new #CamelFolderSummary object
1398  **/
1399 CamelFolderSummary *
1400 camel_folder_summary_new (CamelFolder *folder)
1401 {
1402         return g_object_new (CAMEL_TYPE_FOLDER_SUMMARY, "folder", folder, NULL);
1403 }
1404
1405 /**
1406  * camel_folder_summary_get_index:
1407  * @summary: a #CamelFolderSummary object
1408  *
1409  * Returns: a #CamelFolder to which the summary if associated.
1410  *
1411  * Since: 3.4
1412  **/
1413 CamelFolder *
1414 camel_folder_summary_get_folder (CamelFolderSummary *summary)
1415 {
1416         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1417
1418         return summary->priv->folder;
1419 }
1420
1421 /**
1422  * camel_folder_summary_get_saved_count:
1423  * @summary: a #CamelFolderSummary object
1424  *
1425  * Returns: Count of saved infos.
1426  *
1427  * Since: 3.4
1428  **/
1429 guint32
1430 camel_folder_summary_get_saved_count (CamelFolderSummary *summary)
1431 {
1432         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1433
1434         return summary->priv->saved_count;
1435 }
1436
1437 /**
1438  * camel_folder_summary_get_unread_count:
1439  * @summary: a #CamelFolderSummary object
1440  *
1441  * Returns: Count of unread infos.
1442  *
1443  * Since: 3.4
1444  **/
1445 guint32
1446 camel_folder_summary_get_unread_count (CamelFolderSummary *summary)
1447 {
1448         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1449
1450         return summary->priv->unread_count;
1451 }
1452
1453 /**
1454  * camel_folder_summary_get_deleted_count:
1455  * @summary: a #CamelFolderSummary object
1456  *
1457  * Returns: Count of deleted infos.
1458  *
1459  * Since: 3.4
1460  **/
1461 guint32
1462 camel_folder_summary_get_deleted_count (CamelFolderSummary *summary)
1463 {
1464         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1465
1466         return summary->priv->deleted_count;
1467 }
1468
1469 /**
1470  * camel_folder_summary_get_junk_count:
1471  * @summary: a #CamelFolderSummary object
1472  *
1473  * Returns: Count of junk infos.
1474  *
1475  * Since: 3.4
1476  **/
1477 guint32
1478 camel_folder_summary_get_junk_count (CamelFolderSummary *summary)
1479 {
1480         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1481
1482         return summary->priv->junk_count;
1483 }
1484
1485 /**
1486  * camel_folder_summary_get_junk_not_deleted_count:
1487  * @summary: a #CamelFolderSummary object
1488  *
1489  * Returns: Count of junk and not deleted infos.
1490  *
1491  * Since: 3.4
1492  **/
1493 guint32
1494 camel_folder_summary_get_junk_not_deleted_count (CamelFolderSummary *summary)
1495 {
1496         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1497
1498         return summary->priv->junk_not_deleted_count;
1499 }
1500
1501 /**
1502  * camel_folder_summary_get_visible_count:
1503  * @summary: a #CamelFolderSummary object
1504  *
1505  * Returns: Count of visible (not junk and not deleted) infos.
1506  *
1507  * Since: 3.4
1508  **/
1509 guint32
1510 camel_folder_summary_get_visible_count (CamelFolderSummary *summary)
1511 {
1512         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1513
1514         return summary->priv->visible_count;
1515 }
1516
1517 /**
1518  * camel_folder_summary_set_index:
1519  * @summary: a #CamelFolderSummary object
1520  * @index: a #CamelIndex
1521  *
1522  * Set the index used to index body content.  If the index is %NULL, or
1523  * not set (the default), no indexing of body content will take place.
1524  *
1525  * Unlike earlier behaviour, build_content need not be set to perform indexing.
1526  **/
1527 void
1528 camel_folder_summary_set_index (CamelFolderSummary *summary,
1529                                 CamelIndex *index)
1530 {
1531         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1532
1533         if (index != NULL)
1534                 g_object_ref (index);
1535
1536         if (summary->priv->index != NULL)
1537                 g_object_unref (summary->priv->index);
1538
1539         summary->priv->index = index;
1540 }
1541
1542 /**
1543  * camel_folder_summary_get_index:
1544  * @summary: a #CamelFolderSummary object
1545  *
1546  * Returns: a #CamelIndex used to index body content.
1547  *
1548  * Since: 3.4
1549  **/
1550 CamelIndex *
1551 camel_folder_summary_get_index (CamelFolderSummary *summary)
1552 {
1553         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1554
1555         return summary->priv->index;
1556 }
1557
1558 /**
1559  * camel_folder_summary_set_build_content:
1560  * @summary: a #CamelFolderSummary object
1561  * @state: to build or not to build the content
1562  *
1563  * Set a flag to tell the summary to build the content info summary
1564  * (#CamelMessageInfo.content).  The default is not to build content
1565  * info summaries.
1566  **/
1567 void
1568 camel_folder_summary_set_build_content (CamelFolderSummary *summary,
1569                                         gboolean state)
1570 {
1571         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1572
1573         if (summary->priv->build_content == state)
1574                 return;
1575
1576         summary->priv->build_content = state;
1577
1578         g_object_notify (G_OBJECT (summary), "build-content");
1579 }
1580
1581 /**
1582  * camel_folder_summary_get_build_content:
1583  * @summary: a #CamelFolderSummary object
1584  *
1585  * Returns: Whether to build #CamelMessageInfo.content.
1586  *
1587  * Since: 3.4
1588  **/
1589 gboolean
1590 camel_folder_summary_get_build_content (CamelFolderSummary *summary)
1591 {
1592         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1593
1594         return summary->priv->build_content;
1595 }
1596
1597 /**
1598  * camel_folder_summary_set_need_preview:
1599  *
1600  * Since: 2.28
1601  **/
1602 void
1603 camel_folder_summary_set_need_preview (CamelFolderSummary *summary,
1604                                        gboolean preview)
1605 {
1606         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1607
1608         summary->priv->need_preview = preview;
1609 }
1610
1611 /**
1612  * camel_folder_summary_get_need_preview:
1613  *
1614  * Since: 2.28
1615  **/
1616 gboolean
1617 camel_folder_summary_get_need_preview (CamelFolderSummary *summary)
1618 {
1619         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1620
1621         return summary->priv->need_preview;
1622 }
1623
1624 /**
1625  * camel_folder_summary_next_uid:
1626  * @summary: a #CamelFolderSummary object
1627  *
1628  * Generate a new unique uid value as an integer.  This
1629  * may be used to create a unique sequence of numbers.
1630  *
1631  * Returns: the next unique uid value
1632  **/
1633 guint32
1634 camel_folder_summary_next_uid (CamelFolderSummary *summary)
1635 {
1636         guint32 uid;
1637
1638         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1639
1640         uid = summary->priv->nextuid++;
1641         camel_folder_summary_touch (summary);
1642
1643         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1644
1645         return uid;
1646 }
1647
1648 /**
1649  * camel_folder_summary_set_next_uid:
1650  * @summary: a #CamelFolderSummary object
1651  * @uid: The next minimum uid to assign.  To avoid clashing
1652  * uid's, set this to the uid of a given messages + 1.
1653  *
1654  * Set the next minimum uid available.  This can be used to
1655  * ensure new uid's do not clash with existing uid's.
1656  **/
1657 void
1658 camel_folder_summary_set_next_uid (CamelFolderSummary *summary,
1659                                    guint32 uid)
1660 {
1661         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1662
1663         summary->priv->nextuid = MAX (summary->priv->nextuid, uid);
1664         camel_folder_summary_touch (summary);
1665
1666         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1667 }
1668
1669 /**
1670  * camel_folder_summary_get_next_uid:
1671  * @summary: a #CamelFolderSummary object
1672  *
1673  * Returns: Next uid currently awaiting for assignment. The difference from
1674  *    camel_folder_summary_next_uid() is that this function returns actual
1675  *    value and doesn't increment it before returning.
1676  *
1677  * Since: 3.4
1678  **/
1679 guint32
1680 camel_folder_summary_get_next_uid (CamelFolderSummary *summary)
1681 {
1682         guint32 res;
1683
1684         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1685
1686         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1687
1688         res = summary->priv->nextuid;
1689
1690         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1691
1692         return res;
1693 }
1694
1695 /**
1696  * camel_folder_summary_next_uid_string:
1697  * @summary: a #CamelFolderSummary object
1698  *
1699  * Retrieve the next uid, but as a formatted string.
1700  *
1701  * Returns: the next uid as an unsigned integer string.
1702  * This string must be freed by the caller.
1703  **/
1704 gchar *
1705 camel_folder_summary_next_uid_string (CamelFolderSummary *summary)
1706 {
1707         CamelFolderSummaryClass *class;
1708
1709         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1710
1711         class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
1712         g_return_val_if_fail (class->next_uid_string != NULL, NULL);
1713
1714         return class->next_uid_string (summary);
1715 }
1716
1717 /**
1718  * camel_folder_summary_count:
1719  * @summary: a #CamelFolderSummary object
1720  *
1721  * Get the number of summary items stored in this summary.
1722  *
1723  * Returns: the number of items in the summary
1724  **/
1725 guint
1726 camel_folder_summary_count (CamelFolderSummary *summary)
1727 {
1728         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1729
1730         return g_hash_table_size (summary->priv->uids);
1731 }
1732
1733 /**
1734  * camel_folder_summary_check_uid
1735  * @summary: a #CamelFolderSummary object
1736  * @uid: a uid
1737  *
1738  * Check if the uid is valid. This isn't very efficient, so it shouldn't be called iteratively.
1739  *
1740  *
1741  * Returns: if the uid is present in the summary or not  (%TRUE or %FALSE)
1742  *
1743  * Since: 2.24
1744  **/
1745 gboolean
1746 camel_folder_summary_check_uid (CamelFolderSummary *summary,
1747                                 const gchar *uid)
1748 {
1749         gboolean ret;
1750
1751         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1752         g_return_val_if_fail (uid != NULL, FALSE);
1753
1754         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1755
1756         ret = g_hash_table_lookup_extended (summary->priv->uids, uid, NULL, NULL);
1757
1758         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1759
1760         return ret;
1761 }
1762
1763 static void
1764 folder_summary_dupe_uids_to_array (gpointer key_uid,
1765                                    gpointer value_flags,
1766                                    gpointer user_data)
1767 {
1768         g_ptr_array_add (user_data, (gpointer) camel_pstring_strdup (key_uid));
1769 }
1770
1771 /**
1772  * camel_folder_summary_get_array:
1773  * @summary: a #CamelFolderSummary object
1774  *
1775  * Obtain a copy of the summary array.  This is done atomically,
1776  * so cannot contain empty entries.
1777  *
1778  * Free with camel_folder_summary_free_array()
1779  *
1780  * Returns: a #GPtrArray of uids
1781  *
1782  * Since: 3.4
1783  **/
1784 GPtrArray *
1785 camel_folder_summary_get_array (CamelFolderSummary *summary)
1786 {
1787         GPtrArray *res;
1788
1789         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1790
1791         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1792
1793         res = g_ptr_array_sized_new (g_hash_table_size (summary->priv->uids));
1794         g_hash_table_foreach (summary->priv->uids, folder_summary_dupe_uids_to_array, res);
1795
1796         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1797
1798         return res;
1799 }
1800
1801 /**
1802  * camel_folder_summary_free_array:
1803  * @array: a #GPtrArray returned from camel_folder_summary_get_array()
1804  *
1805  * Free's array and its elements returned from camel_folder_summary_get_array().
1806  *
1807  * Since: 3.4
1808  **/
1809 void
1810 camel_folder_summary_free_array (GPtrArray *array)
1811 {
1812         if (!array)
1813                 return;
1814
1815         g_ptr_array_foreach (array, (GFunc) camel_pstring_free, NULL);
1816         g_ptr_array_free (array, TRUE);
1817 }
1818
1819 static void
1820 cfs_copy_uids_cb (gpointer key,
1821                   gpointer value,
1822                   gpointer user_data)
1823 {
1824         const gchar *uid = key;
1825         GHashTable *copy_hash = user_data;
1826
1827         g_hash_table_insert (copy_hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
1828 }
1829
1830 /**
1831  * camel_folder_summary_get_hash:
1832  * @summary: a #CamelFolderSummary object
1833  *
1834  * Returns hash of current stored 'uids' in summary, where key is 'uid'
1835  * from the string pool, and value is 1. The returned pointer should
1836  * be freed with g_hash_table_destroy().
1837  *
1838  * Note: When searching for values always use uids from the string pool.
1839  *
1840  * Since: 3.6
1841  **/
1842 GHashTable *
1843 camel_folder_summary_get_hash (CamelFolderSummary *summary)
1844 {
1845         GHashTable *uids;
1846
1847         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1848
1849         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1850
1851         /* using direct hash because of strings being from the string pool */
1852         uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
1853         g_hash_table_foreach (summary->priv->uids, cfs_copy_uids_cb, uids);
1854
1855         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1856
1857         return uids;
1858 }
1859
1860 /**
1861  * camel_folder_summary_peek_loaded:
1862  *
1863  * Since: 2.26
1864  **/
1865 CamelMessageInfo *
1866 camel_folder_summary_peek_loaded (CamelFolderSummary *summary,
1867                                   const gchar *uid)
1868 {
1869         CamelMessageInfo *info;
1870
1871         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1872
1873         info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1874
1875         if (info)
1876                 camel_message_info_ref (info);
1877
1878         return info;
1879 }
1880
1881 struct _db_pass_data {
1882         GHashTable *columns_hash;
1883         CamelFolderSummary *summary;
1884         gboolean add; /* or just insert to hashtable */
1885 };
1886
1887 static CamelMessageInfo *
1888 message_info_from_uid (CamelFolderSummary *summary,
1889                        const gchar *uid)
1890 {
1891         CamelMessageInfo *info;
1892         gint ret;
1893
1894         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1895
1896         info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1897
1898         if (!info) {
1899                 CamelDB *cdb;
1900                 CamelStore *parent_store;
1901                 const gchar *folder_name;
1902                 struct _db_pass_data data;
1903
1904                 folder_name = camel_folder_get_full_name (summary->priv->folder);
1905
1906                 if (is_in_memory_summary (summary)) {
1907                         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1908                         g_warning ("%s: Tried to load uid '%s' from DB on in-memory summary of '%s'",
1909                                 G_STRFUNC, uid, folder_name);
1910                         return NULL;
1911                 }
1912
1913                 parent_store = camel_folder_get_parent_store (summary->priv->folder);
1914                 cdb = parent_store->cdb_r;
1915
1916                 data.columns_hash = NULL;
1917                 data.summary = summary;
1918                 data.add = FALSE;
1919
1920                 ret = camel_db_read_message_info_record_with_uid (
1921                         cdb, folder_name, uid, &data,
1922                         camel_read_mir_callback, NULL);
1923                 if (data.columns_hash)
1924                         g_hash_table_destroy (data.columns_hash);
1925
1926                 if (ret != 0) {
1927                         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1928                         return NULL;
1929                 }
1930
1931                 /* We would have double reffed at camel_read_mir_callback */
1932                 info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1933
1934                 cfs_schedule_info_release_timer (summary);
1935         }
1936
1937         if (info)
1938                 camel_message_info_ref (info);
1939
1940         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
1941
1942         return info;
1943 }
1944
1945 /**
1946  * camel_folder_summary_get:
1947  * @summary: a #CamelFolderSummary object
1948  * @uid: a uid
1949  *
1950  * Retrieve a summary item by uid.
1951  *
1952  * A referenced to the summary item is returned, which may be
1953  * ref'd or free'd as appropriate.
1954  *
1955  * Returns: the summary item, or %NULL if the uid @uid is not available
1956  *
1957  * Since: 3.4
1958  **/
1959 CamelMessageInfo *
1960 camel_folder_summary_get (CamelFolderSummary *summary,
1961                           const gchar *uid)
1962 {
1963         CamelFolderSummaryClass *class;
1964
1965         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1966         g_return_val_if_fail (uid != NULL, NULL);
1967
1968         class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
1969         g_return_val_if_fail (class->message_info_from_uid != NULL, NULL);
1970
1971         return class->message_info_from_uid (summary, uid);
1972 }
1973
1974 static CamelMessageContentInfo *
1975 perform_content_info_load_from_db (CamelFolderSummary *summary,
1976                                    CamelMIRecord *mir)
1977 {
1978         gint i;
1979         guint32 count;
1980         CamelMessageContentInfo *ci, *pci;
1981         gchar *part;
1982
1983         ci = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_from_db (summary, mir);
1984         if (ci == NULL)
1985                 return NULL;
1986         part = mir->cinfo;
1987         if (!part)
1988                 return ci;
1989         if (*part == ' ') part++;
1990         count = bdata_extract_digit (&part);
1991
1992         mir->cinfo = part;
1993         for (i = 0; i < count; i++) {
1994                 pci = perform_content_info_load_from_db (summary, mir);
1995                 if (pci ) {
1996                         my_list_append ((struct _node **) &ci->childs, (struct _node *) pci);
1997                         pci->parent = ci;
1998                 } else {
1999                         d (fprintf (stderr, "Summary file format messed up?"));
2000                         camel_folder_summary_content_info_free (summary, ci);
2001                         return NULL;
2002                 }
2003         }
2004         return ci;
2005 }
2006
2007 static void
2008 append_changed_uids (gchar *key,
2009                      CamelMessageInfoBase *info,
2010                      GPtrArray *array)
2011 {
2012         if (info->dirty || info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED)
2013                 g_ptr_array_add (array, (gpointer) camel_pstring_strdup ((camel_message_info_uid (info))));
2014 }
2015
2016 /**
2017  * camel_folder_summary_get_changed:
2018  *
2019  * Since: 2.24
2020  **/
2021 GPtrArray *
2022 camel_folder_summary_get_changed (CamelFolderSummary *summary)
2023 {
2024         GPtrArray *res = g_ptr_array_new ();
2025
2026         /* FIXME[disk-summary] sucks, this function returns from memory.
2027          * We need to have collate or something to get the modified ones
2028          * from DB and merge */
2029
2030         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2031         g_hash_table_foreach (summary->priv->loaded_infos, (GHFunc) append_changed_uids, res);
2032         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2033
2034         return res;
2035 }
2036
2037 static void
2038 count_changed_uids (gchar *key,
2039                     CamelMessageInfoBase *info,
2040                     gint *count)
2041 {
2042         if (info->dirty)
2043                 (*count)++;
2044 }
2045
2046 static gint
2047 cfs_count_dirty (CamelFolderSummary *summary)
2048 {
2049         gint count = 0;
2050
2051         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2052         g_hash_table_foreach (summary->priv->loaded_infos, (GHFunc) count_changed_uids, &count);
2053         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2054
2055         return count;
2056 }
2057
2058 static gboolean
2059 remove_item (gchar *uid,
2060              CamelMessageInfoBase *info,
2061              GSList **to_remove_infos)
2062 {
2063         if (info->refcount == 1 && !info->dirty && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
2064                 *to_remove_infos = g_slist_prepend (*to_remove_infos, info);
2065                 return TRUE;
2066         }
2067
2068         return FALSE;
2069 }
2070
2071 static void
2072 remove_cache (CamelSession *session,
2073               GCancellable *cancellable,
2074               CamelFolderSummary *summary,
2075               GError **error)
2076 {
2077         GSList *to_remove_infos = NULL;
2078
2079         CAMEL_DB_RELEASE_SQLITE_MEMORY;
2080
2081         if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP)
2082                 return;
2083
2084         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2085
2086         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
2087         g_hash_table_foreach_remove (summary->priv->loaded_infos, (GHRFunc) remove_item, &to_remove_infos);
2088         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
2089
2090         g_slist_foreach (to_remove_infos, (GFunc) camel_message_info_free, NULL);
2091         g_slist_free (to_remove_infos);
2092
2093         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2094
2095         summary->priv->cache_load_time = time (NULL);
2096 }
2097
2098 static gboolean
2099 cfs_try_release_memory (CamelFolderSummary *summary)
2100 {
2101         CamelStore *parent_store;
2102         CamelSession *session;
2103
2104         /* If folder is freed or if the cache is nil then clean up */
2105         if (!summary->priv->folder ||
2106             !g_hash_table_size (summary->priv->loaded_infos) ||
2107             is_in_memory_summary (summary)) {
2108                 summary->priv->cache_load_time = 0;
2109                 summary->priv->timeout_handle = 0;
2110                 g_object_unref (summary);
2111
2112                 return FALSE;
2113         }
2114
2115         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2116         session = camel_service_get_session (CAMEL_SERVICE (parent_store));
2117
2118         if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP)
2119                 return TRUE;
2120
2121         camel_session_submit_job (
2122                 session,
2123                 (CamelSessionCallback) remove_cache,
2124                 g_object_ref (summary),
2125                 (GDestroyNotify) g_object_unref);
2126
2127         return TRUE;
2128 }
2129
2130 static void
2131 cfs_schedule_info_release_timer (CamelFolderSummary *summary)
2132 {
2133         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
2134
2135         if (is_in_memory_summary (summary))
2136                 return;
2137
2138         if (!summary->priv->timeout_handle) {
2139                 static gboolean know_can_do = FALSE, can_do = TRUE;
2140
2141                 if (!know_can_do) {
2142                         can_do = !g_getenv ("CAMEL_FREE_INFOS");
2143                         know_can_do = TRUE;
2144                 }
2145
2146                 /* FIXME[disk-summary] LRU please and not timeouts */
2147                 if (can_do) {
2148                         summary->priv->timeout_handle = g_timeout_add_seconds (SUMMARY_CACHE_DROP,
2149                                 (GSourceFunc) cfs_try_release_memory, g_object_ref (summary));
2150                 }
2151         }
2152
2153         /* update also cache load time to the actual, to not release something just loaded */
2154         summary->priv->cache_load_time = time (NULL);
2155 }
2156
2157 static gint
2158 cfs_cache_size (CamelFolderSummary *summary)
2159 {
2160         /* FIXME[disk-summary] this is a timely hack. fix it well */
2161         if (!CAMEL_IS_VEE_FOLDER (summary->priv->folder))
2162                 return g_hash_table_size (summary->priv->loaded_infos);
2163         else
2164                 return g_hash_table_size (summary->priv->uids);
2165 }
2166
2167 /* Update preview of cached messages */
2168
2169 static void
2170 msg_update_preview (const gchar *uid,
2171                     gpointer value,
2172                     CamelFolder *folder)
2173 {
2174         CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
2175         CamelMimeMessage *msg;
2176         CamelStore *parent_store;
2177         const gchar *full_name;
2178
2179         full_name = camel_folder_get_full_name (folder);
2180         parent_store = camel_folder_get_parent_store (folder);
2181
2182         /* FIXME Pass a GCancellable */
2183         msg = camel_folder_get_message_sync (folder, uid, NULL, NULL);
2184         if (msg != NULL) {
2185                 if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview) {
2186                         if (!is_in_memory_summary (folder->summary))
2187                                 camel_db_write_preview_record (parent_store->cdb_w, full_name, info->uid, info->preview, NULL);
2188                 }
2189         }
2190         camel_message_info_free (info);
2191 }
2192
2193 static void
2194 pick_uids (const gchar *uid,
2195            CamelMessageInfoBase *mi,
2196            GPtrArray *array)
2197 {
2198         if (mi->preview)
2199                 g_ptr_array_add (array, (gchar *) camel_pstring_strdup (uid));
2200 }
2201
2202 static void
2203 copy_all_uids_to_hash (gpointer uid,
2204                        gpointer hash)
2205 {
2206         g_return_if_fail (uid != NULL);
2207
2208         g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
2209 }
2210
2211 static gboolean
2212 fill_mi (const gchar *uid,
2213          const gchar *msg,
2214          CamelFolder *folder)
2215 {
2216         CamelMessageInfoBase *info;
2217
2218         info = g_hash_table_lookup (folder->summary->priv->loaded_infos, uid);
2219         if (info) /* We re assign the memory of msg */
2220                 info->preview = (gchar *) msg;
2221         camel_pstring_free (uid); /* unref the uid */
2222
2223         return TRUE;
2224 }
2225
2226 static void
2227 preview_update (CamelSession *session,
2228                 GCancellable *cancellable,
2229                 CamelFolder *folder,
2230                 GError **error)
2231 {
2232         /* FIXME: Either lock & use or copy & use.*/
2233         GPtrArray *uids_uncached, *uids_array;
2234         GHashTable *preview_data, *uids_hash;
2235         CamelStore *parent_store;
2236         const gchar *full_name;
2237         gboolean is_in_memory = is_in_memory_summary (folder->summary);
2238         gint i;
2239
2240         uids_array = camel_folder_summary_get_array (folder->summary);
2241         uids_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
2242         g_ptr_array_foreach (uids_array, copy_all_uids_to_hash, uids_hash);
2243         uids_uncached = camel_folder_get_uncached_uids (folder, uids_array, NULL);
2244         camel_folder_summary_free_array (uids_array);
2245         uids_array = NULL;
2246
2247         full_name = camel_folder_get_full_name (folder);
2248         parent_store = camel_folder_get_parent_store (folder);
2249         preview_data = is_in_memory ? NULL : camel_db_get_folder_preview (parent_store->cdb_r, full_name, NULL);
2250         if (preview_data) {
2251                 g_hash_table_foreach_remove (preview_data, (GHRFunc) fill_mi, folder);
2252                 g_hash_table_destroy (preview_data);
2253         }
2254
2255         camel_folder_summary_lock (folder->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2256         g_hash_table_foreach (folder->summary->priv->loaded_infos, (GHFunc) pick_uids, uids_uncached);
2257         camel_folder_summary_unlock (folder->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2258
2259         for (i = 0; i < uids_uncached->len; i++) {
2260                 g_hash_table_remove (uids_hash, uids_uncached->pdata[i]);
2261         }
2262
2263         camel_folder_lock (folder, CAMEL_FOLDER_REC_LOCK);
2264         if (!is_in_memory)
2265                 camel_db_begin_transaction (parent_store->cdb_w, NULL);
2266         g_hash_table_foreach (uids_hash, (GHFunc) msg_update_preview, folder);
2267         if (!is_in_memory)
2268                 camel_db_end_transaction (parent_store->cdb_w, NULL);
2269         camel_folder_unlock (folder, CAMEL_FOLDER_REC_LOCK);
2270         camel_folder_free_uids (folder, uids_uncached);
2271         g_hash_table_destroy (uids_hash);
2272 }
2273
2274 /* end */
2275
2276 static gint
2277 cfs_reload_from_db (CamelFolderSummary *summary,
2278                     GError **error)
2279 {
2280         CamelDB *cdb;
2281         CamelSession *session;
2282         CamelStore *parent_store;
2283         const gchar *folder_name;
2284         gint ret = 0;
2285         struct _db_pass_data data;
2286
2287         /* FIXME[disk-summary] baseclass this, and vfolders we may have to
2288          * load better. */
2289         d (printf ("\ncamel_folder_summary_reload_from_db called \n"));
2290
2291         if (is_in_memory_summary (summary))
2292                 return 0;
2293
2294         folder_name = camel_folder_get_full_name (summary->priv->folder);
2295         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2296         session = camel_service_get_session (CAMEL_SERVICE (parent_store));
2297         cdb = parent_store->cdb_r;
2298
2299         data.columns_hash = NULL;
2300         data.summary = summary;
2301         data.add = FALSE;
2302
2303         ret = camel_db_read_message_info_records (
2304                 cdb, folder_name, (gpointer) &data,
2305                 camel_read_mir_callback, NULL);
2306
2307         if (data.columns_hash)
2308                 g_hash_table_destroy (data.columns_hash);
2309
2310         cfs_schedule_info_release_timer (summary);
2311
2312         if (summary->priv->need_preview)
2313                 camel_session_submit_job (
2314                         session,
2315                         (CamelSessionCallback) preview_update,
2316                         g_object_ref (summary->priv->folder),
2317                         (GDestroyNotify) g_object_unref);
2318
2319         return ret == 0 ? 0 : -1;
2320 }
2321
2322 /**
2323  * camel_folder_summary_add_preview:
2324  *
2325  * Since: 2.28
2326  **/
2327 void
2328 camel_folder_summary_add_preview (CamelFolderSummary *summary,
2329                                   CamelMessageInfo *info)
2330 {
2331         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2332         g_hash_table_insert (summary->priv->preview_updates, (gchar *) info->uid, ((CamelMessageInfoBase *) info)->preview);
2333         camel_folder_summary_touch (summary);
2334         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2335 }
2336
2337 /**
2338  * camel_folder_summary_prepare_fetch_all:
2339  * @summary: #CamelFolderSummary object
2340  * @error: return location for a #GError, or %NULL
2341  *
2342  * Loads all infos into memory, if they are not yet and ensures
2343  * they will not be freed in next couple minutes. Call this function
2344  * before any mass operation or when all message infos will be needed,
2345  * for better performance.
2346  *
2347  * Since: 2.32
2348  **/
2349 void
2350 camel_folder_summary_prepare_fetch_all (CamelFolderSummary *summary,
2351                                         GError **error)
2352 {
2353         guint loaded, known;
2354
2355         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
2356
2357         loaded = cfs_cache_size (summary);
2358         known = camel_folder_summary_count (summary);
2359
2360         if (known - loaded > 50) {
2361                 camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2362                 cfs_reload_from_db (summary, error);
2363                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2364         }
2365
2366         /* update also cache load time, even when not loaded anything */
2367         summary->priv->cache_load_time = time (NULL);
2368 }
2369
2370 /**
2371  * camel_folder_summary_load_from_db:
2372  *
2373  * Since: 2.24
2374  **/
2375 gboolean
2376 camel_folder_summary_load_from_db (CamelFolderSummary *summary,
2377                                    GError **error)
2378 {
2379         CamelDB *cdb;
2380         CamelStore *parent_store;
2381         const gchar *full_name;
2382         gint ret = 0;
2383         GError *local_error = NULL;
2384
2385         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2386
2387         if (is_in_memory_summary (summary))
2388                 return TRUE;
2389
2390         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2391         camel_folder_summary_save_to_db (summary, NULL);
2392
2393         /* struct _db_pass_data data; */
2394         d (printf ("\ncamel_folder_summary_load_from_db called \n"));
2395
2396         full_name = camel_folder_get_full_name (summary->priv->folder);
2397         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2398         if (!camel_folder_summary_header_load_from_db (summary, parent_store, full_name, error)) {
2399                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2400                 return FALSE;
2401         }
2402
2403         cdb = parent_store->cdb_r;
2404
2405         ret = camel_db_get_folder_uids (
2406                 cdb, full_name, summary->sort_by, summary->collate,
2407                 summary->priv->uids, &local_error);
2408
2409         if (local_error != NULL && local_error->message != NULL &&
2410             strstr (local_error->message, "no such table") != NULL) {
2411                 g_clear_error (&local_error);
2412
2413                 /* create table the first time it is accessed and missing */
2414                 ret = camel_db_prepare_message_info_table (cdb, full_name, error);
2415         } else if (local_error != NULL)
2416                 g_propagate_error (error, local_error);
2417
2418         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2419
2420         return ret == 0;
2421 }
2422
2423 static void
2424 mir_from_cols (CamelMIRecord *mir,
2425                CamelFolderSummary *summary,
2426                GHashTable **columns_hash,
2427                gint ncol,
2428                gchar **cols,
2429                gchar **name)
2430 {
2431         gint i;
2432
2433         for (i = 0; i < ncol; ++i) {
2434                 if (!name[i] || !cols[i])
2435                         continue;
2436
2437                 switch (camel_db_get_column_ident (columns_hash, i, ncol, name)) {
2438                         case CAMEL_DB_COLUMN_UID:
2439                                 mir->uid = (gchar *) camel_pstring_strdup (cols[i]);
2440                                 break;
2441                         case CAMEL_DB_COLUMN_FLAGS:
2442                                 mir->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2443                                 break;
2444                         case CAMEL_DB_COLUMN_READ:
2445                                 mir->read =  (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2446                                 break;
2447                         case CAMEL_DB_COLUMN_DELETED:
2448                                 mir->deleted = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2449                                 break;
2450                         case CAMEL_DB_COLUMN_REPLIED:
2451                                 mir->replied = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2452                                 break;
2453                         case CAMEL_DB_COLUMN_IMPORTANT:
2454                                 mir->important = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2455                                 break;
2456                         case CAMEL_DB_COLUMN_JUNK:
2457                                 mir->junk = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2458                                 break;
2459                         case CAMEL_DB_COLUMN_ATTACHMENT:
2460                                 mir->attachment = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
2461                                 break;
2462                         case CAMEL_DB_COLUMN_SIZE:
2463                                 mir->size =  cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2464                                 break;
2465                         case CAMEL_DB_COLUMN_DSENT:
2466                                 mir->dsent = cols[i] ? strtol (cols[i], NULL, 10) : 0;
2467                                 break;
2468                         case CAMEL_DB_COLUMN_DRECEIVED:
2469                                 mir->dreceived = cols[i] ? strtol (cols[i], NULL, 10) : 0;
2470                                 break;
2471                         case CAMEL_DB_COLUMN_SUBJECT:
2472                                 mir->subject = (gchar *) camel_pstring_strdup (cols[i]);
2473                                 break;
2474                         case CAMEL_DB_COLUMN_MAIL_FROM:
2475                                 mir->from = (gchar *) camel_pstring_strdup (cols[i]);
2476                                 break;
2477                         case CAMEL_DB_COLUMN_MAIL_TO:
2478                                 mir->to = (gchar *) camel_pstring_strdup (cols[i]);
2479                                 break;
2480                         case CAMEL_DB_COLUMN_MAIL_CC:
2481                                 mir->cc = (gchar *) camel_pstring_strdup (cols[i]);
2482                                 break;
2483                         case CAMEL_DB_COLUMN_MLIST:
2484                                 mir->mlist = (gchar *) camel_pstring_strdup (cols[i]);
2485                                 break;
2486                         case CAMEL_DB_COLUMN_FOLLOWUP_FLAG:
2487                                 mir->followup_flag = (gchar *) camel_pstring_strdup (cols[i]);
2488                                 break;
2489                         case CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON:
2490                                 mir->followup_completed_on = (gchar *) camel_pstring_strdup (cols[i]);
2491                                 break;
2492                         case CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY:
2493                                 mir->followup_due_by = (gchar *) camel_pstring_strdup (cols[i]);
2494                                 break;
2495                         case CAMEL_DB_COLUMN_PART:
2496                                 mir->part = g_strdup (cols[i]);
2497                                 break;
2498                         case CAMEL_DB_COLUMN_LABELS:
2499                                 mir->labels = g_strdup (cols[i]);
2500                                 break;
2501                         case CAMEL_DB_COLUMN_USERTAGS:
2502                                 mir->usertags = g_strdup (cols[i]);
2503                                 break;
2504                         case CAMEL_DB_COLUMN_CINFO:
2505                                 mir->cinfo = g_strdup (cols[i]);
2506                                 break;
2507                         case CAMEL_DB_COLUMN_BDATA:
2508                                 mir->bdata = g_strdup (cols[i]);
2509                                 break;
2510                         case CAMEL_DB_COLUMN_BODYSTRUCTURE:
2511                                 /* Evolution itself doesn't yet use this, ignoring */
2512                                 /* mir->bodystructure = g_strdup (cols[i]); */
2513                                 break;
2514                         default:
2515                                 g_warn_if_reached ();
2516                                 break;
2517                 }
2518         }
2519 }
2520
2521 static gint
2522 camel_read_mir_callback (gpointer ref,
2523                          gint ncol,
2524                          gchar **cols,
2525                          gchar **name)
2526 {
2527         struct _db_pass_data *data = (struct _db_pass_data *) ref;
2528         CamelFolderSummary *summary = data->summary;
2529         CamelMIRecord *mir;
2530         CamelMessageInfo *info;
2531         gint ret = 0;
2532
2533         mir = g_new0 (CamelMIRecord , 1);
2534         mir_from_cols (mir, summary, &data->columns_hash, ncol, cols, name);
2535
2536         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2537         if (!mir->uid || g_hash_table_lookup (summary->priv->loaded_infos, mir->uid)) {
2538                 /* Unlock and better return */
2539                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2540                 camel_db_camel_mir_free (mir);
2541                 return ret;
2542         }
2543         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2544
2545         info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_from_db (summary, mir);
2546
2547         if (info) {
2548
2549                 if (summary->priv->build_content) {
2550                         gchar *tmp;
2551                         tmp = mir->cinfo;
2552                         /* FIXME: this should be done differently, how i don't know */
2553                         ((CamelMessageInfoBase *) info)->content = perform_content_info_load_from_db (summary, mir);
2554                         if (((CamelMessageInfoBase *) info)->content == NULL) {
2555                                 camel_message_info_free (info);
2556                                 info = NULL;
2557                         }
2558                         mir->cinfo = tmp;
2559
2560                         if (!info) {
2561                                 camel_db_camel_mir_free (mir);
2562                                 return -1;
2563                         }
2564                 }
2565
2566                 /* Just now we are reading from the DB, it can't be dirty. */
2567                 ((CamelMessageInfoBase *) info)->dirty = FALSE;
2568                 if (data->add)
2569                         camel_folder_summary_add (summary, info);
2570                 else
2571                         camel_folder_summary_insert (summary, info, TRUE);
2572
2573         } else {
2574                 g_warning ("Loading messageinfo from db failed");
2575                 ret = -1;
2576         }
2577
2578         camel_db_camel_mir_free (mir);
2579
2580         return ret;
2581 }
2582
2583 /* saves the content descriptions, recursively */
2584 static gboolean
2585 perform_content_info_save_to_db (CamelFolderSummary *summary,
2586                                  CamelMessageContentInfo *ci,
2587                                  CamelMIRecord *record)
2588 {
2589         CamelMessageContentInfo *part;
2590         gchar *oldr;
2591
2592         if (!CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_to_db (summary, ci, record))
2593                 return FALSE;
2594
2595         oldr = record->cinfo;
2596         record->cinfo = g_strdup_printf ("%s %d", oldr, my_list_size ((struct _node **) &ci->childs));
2597         g_free (oldr);
2598
2599         part = ci->childs;
2600         while (part) {
2601                 if (perform_content_info_save_to_db (summary, part, record) == -1)
2602                         return FALSE;
2603                 part = part->next;
2604         }
2605
2606         return TRUE;
2607 }
2608
2609 typedef struct {
2610         GError **error;
2611         gboolean migration;
2612         gint progress;
2613 } SaveToDBArgs;
2614
2615 static void
2616 save_to_db_cb (gpointer key,
2617                gpointer value,
2618                gpointer data)
2619 {
2620         SaveToDBArgs *args = (SaveToDBArgs *) data;
2621         GError **error = args->error;
2622         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) value;
2623         CamelFolderSummary *summary = (CamelFolderSummary *) mi->summary;
2624         CamelStore *parent_store;
2625         const gchar *full_name;
2626         CamelDB *cdb;
2627         CamelMIRecord *mir;
2628
2629         full_name = camel_folder_get_full_name (summary->priv->folder);
2630         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2631         cdb = parent_store->cdb_w;
2632
2633         if (!args->migration && !mi->dirty)
2634                 return;
2635
2636         mir = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_to_db (summary, (CamelMessageInfo *) mi);
2637
2638         if (mir && summary->priv->build_content) {
2639                 if (!perform_content_info_save_to_db (summary, ((CamelMessageInfoBase *) mi)->content, mir)) {
2640                         g_warning ("unable to save mir+cinfo for uid: %s\n", mir->uid);
2641                         camel_db_camel_mir_free (mir);
2642                         /* FIXME: Add exception here */
2643                         return;
2644                 }
2645         }
2646
2647         g_return_if_fail (mir != NULL);
2648
2649         if (!args->migration) {
2650                 if (camel_db_write_message_info_record (cdb, full_name, mir, error) != 0) {
2651                         camel_db_camel_mir_free (mir);
2652                         return;
2653                 }
2654         } else {
2655                 if (camel_db_write_fresh_message_info_record (cdb, CAMEL_DB_IN_MEMORY_TABLE, mir, error) != 0) {
2656                         camel_db_camel_mir_free (mir);
2657                         return;
2658                 }
2659
2660                 if (args->progress > CAMEL_DB_IN_MEMORY_TABLE_LIMIT) {
2661                     g_print ("BULK INsert limit reached \n");
2662                         camel_db_flush_in_memory_transactions (cdb, full_name, error);
2663                         camel_db_start_in_memory_transactions (cdb, error);
2664                         args->progress = 0;
2665                 } else {
2666                         args->progress++;
2667                 }
2668         }
2669
2670         /* Reset the dirty flag which decides if the changes are synced to the DB or not.
2671         The FOLDER_FLAGGED should be used to check if the changes are synced to the server.
2672         So, dont unset the FOLDER_FLAGGED flag */
2673         mi->dirty = FALSE;
2674
2675         camel_db_camel_mir_free (mir);
2676 }
2677
2678 static gint
2679 save_message_infos_to_db (CamelFolderSummary *summary,
2680                           gboolean fresh_mirs,
2681                           GError **error)
2682 {
2683         CamelStore *parent_store;
2684         CamelDB *cdb;
2685         const gchar *full_name;
2686         SaveToDBArgs args;
2687
2688         if (is_in_memory_summary (summary))
2689                 return 0;
2690
2691         args.error = error;
2692         args.migration = fresh_mirs;
2693         args.progress = 0;
2694
2695         full_name = camel_folder_get_full_name (summary->priv->folder);
2696         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2697         cdb = parent_store->cdb_w;
2698
2699         if (camel_db_prepare_message_info_table (cdb, full_name, error) != 0)
2700                 return -1;
2701
2702         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2703
2704         /* Push MessageInfo-es */
2705         camel_db_begin_transaction (cdb, NULL);
2706         g_hash_table_foreach (summary->priv->loaded_infos, save_to_db_cb, &args);
2707         camel_db_end_transaction (cdb, NULL);
2708
2709         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2710         cfs_schedule_info_release_timer (summary);
2711
2712         return 0;
2713 }
2714
2715 static void
2716 msg_save_preview (const gchar *uid,
2717                   gpointer value,
2718                   CamelFolder *folder)
2719 {
2720         CamelStore *parent_store;
2721         const gchar *full_name;
2722
2723         full_name = camel_folder_get_full_name (folder);
2724         parent_store = camel_folder_get_parent_store (folder);
2725
2726         camel_db_write_preview_record (
2727                 parent_store->cdb_w, full_name, uid, (gchar *) value, NULL);
2728 }
2729
2730 /**
2731  * camel_folder_summary_save_to_db:
2732  *
2733  * Since: 2.24
2734  **/
2735 gboolean
2736 camel_folder_summary_save_to_db (CamelFolderSummary *summary,
2737                                  GError **error)
2738 {
2739         CamelStore *parent_store;
2740         CamelDB *cdb;
2741         CamelFIRecord *record;
2742         gint ret, count;
2743
2744         g_return_val_if_fail (summary != NULL, FALSE);
2745
2746         if (!(summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) ||
2747             is_in_memory_summary (summary))
2748                 return TRUE;
2749
2750         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2751         cdb = parent_store->cdb_w;
2752
2753         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2754
2755         d (printf ("\ncamel_folder_summary_save_to_db called \n"));
2756         if (summary->priv->need_preview && g_hash_table_size (summary->priv->preview_updates)) {
2757                 camel_db_begin_transaction (parent_store->cdb_w, NULL);
2758                 g_hash_table_foreach (summary->priv->preview_updates, (GHFunc) msg_save_preview, summary->priv->folder);
2759                 g_hash_table_remove_all (summary->priv->preview_updates);
2760                 camel_db_end_transaction (parent_store->cdb_w, NULL);
2761         }
2762
2763         summary->flags &= ~CAMEL_FOLDER_SUMMARY_DIRTY;
2764
2765         count = cfs_count_dirty (summary);
2766         if (!count) {
2767                 gboolean res = camel_folder_summary_header_save_to_db (summary, error);
2768                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2769                 return res;
2770         }
2771
2772         ret = save_message_infos_to_db (summary, FALSE, error);
2773         if (ret != 0) {
2774                 /* Failed, so lets reset the flag */
2775                 summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2776                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2777                 return FALSE;
2778         }
2779
2780         /* XXX So... if an error is set, how do we even reach this point
2781          *     given the above error check?  Oye vey this logic is nasty. */
2782         if (error != NULL && *error != NULL &&
2783                 strstr ((*error)->message, "26 columns but 28 values") != NULL) {
2784                 const gchar *full_name;
2785
2786                 full_name = camel_folder_get_full_name (summary->priv->folder);
2787                 g_warning ("Fixing up a broken summary migration on %s\n", full_name);
2788
2789                 /* Begin everything again. */
2790                 camel_db_begin_transaction (cdb, NULL);
2791                 camel_db_reset_folder_version (cdb, full_name, 0, NULL);
2792                 camel_db_end_transaction (cdb, NULL);
2793
2794                 ret = save_message_infos_to_db (summary, FALSE, error);
2795                 if (ret != 0) {
2796                         summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2797                         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2798                         return FALSE;
2799                 }
2800         }
2801
2802         record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (summary, error);
2803         if (!record) {
2804                 summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2805                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2806                 return FALSE;
2807         }
2808
2809         camel_db_begin_transaction (cdb, NULL);
2810         ret = camel_db_write_folder_info_record (cdb, record, error);
2811         g_free (record->folder_name);
2812         g_free (record->bdata);
2813         g_free (record);
2814
2815         if (ret != 0) {
2816                 camel_db_abort_transaction (cdb, NULL);
2817                 summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2818                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2819                 return FALSE;
2820         }
2821
2822         camel_db_end_transaction (cdb, NULL);
2823         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2824
2825         return ret == 0;
2826 }
2827
2828 /**
2829  * camel_folder_summary_header_save_to_db:
2830  *
2831  * Since: 2.24
2832  **/
2833 gboolean
2834 camel_folder_summary_header_save_to_db (CamelFolderSummary *summary,
2835                                         GError **error)
2836 {
2837         CamelStore *parent_store;
2838         CamelFIRecord *record;
2839         CamelDB *cdb;
2840         gint ret;
2841
2842         if (is_in_memory_summary (summary))
2843                 return TRUE;
2844
2845         parent_store = camel_folder_get_parent_store (summary->priv->folder);
2846         cdb = parent_store->cdb_w;
2847         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2848
2849         d (printf ("\ncamel_folder_summary_header_save_to_db called \n"));
2850
2851         record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (summary, error);
2852         if (!record) {
2853                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2854                 return FALSE;
2855         }
2856
2857         camel_db_begin_transaction (cdb, NULL);
2858         ret = camel_db_write_folder_info_record (cdb, record, error);
2859         g_free (record->folder_name);
2860         g_free (record->bdata);
2861         g_free (record);
2862
2863         if (ret != 0) {
2864                 camel_db_abort_transaction (cdb, NULL);
2865                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2866                 return FALSE;
2867         }
2868
2869         camel_db_end_transaction (cdb, NULL);
2870         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2871
2872         return ret == 0;
2873 }
2874
2875 /**
2876  * camel_folder_summary_header_load_from_db:
2877  *
2878  * Since: 2.24
2879  **/
2880 gboolean
2881 camel_folder_summary_header_load_from_db (CamelFolderSummary *summary,
2882                                           CamelStore *store,
2883                                           const gchar *folder_name,
2884                                           GError **error)
2885 {
2886         CamelDB *cdb;
2887         CamelFIRecord *record;
2888         gboolean ret = FALSE;
2889
2890         d (printf ("\ncamel_folder_summary_header_load_from_db called \n"));
2891
2892         if (is_in_memory_summary (summary))
2893                 return TRUE;
2894
2895         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2896         camel_folder_summary_save_to_db (summary, NULL);
2897
2898         cdb = store->cdb_r;
2899
2900         record = g_new0 (CamelFIRecord, 1);
2901         camel_db_read_folder_info_record (cdb, folder_name, record, error);
2902
2903         ret = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_from_db (summary, record);
2904
2905         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2906
2907         g_free (record->folder_name);
2908         g_free (record->bdata);
2909         g_free (record);
2910
2911         return ret;
2912 }
2913
2914 static gboolean
2915 summary_assign_uid (CamelFolderSummary *summary,
2916                     CamelMessageInfo *info)
2917 {
2918         const gchar *uid;
2919         CamelMessageInfo *mi;
2920
2921         uid = camel_message_info_uid (info);
2922
2923         if (uid == NULL || uid[0] == 0) {
2924                 camel_pstring_free (info->uid);
2925                 uid = info->uid = (gchar *) camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
2926         }
2927
2928         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2929
2930         while ((mi = g_hash_table_lookup (summary->priv->loaded_infos, uid))) {
2931                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2932
2933                 if (mi == info)
2934                         return FALSE;
2935
2936                 d (printf ("Trying to insert message with clashing uid (%s).  new uid re-assigned", camel_message_info_uid (info)));
2937
2938                 camel_pstring_free (info->uid);
2939                 uid = info->uid = camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
2940                 camel_message_info_set_flags (info, CAMEL_MESSAGE_FOLDER_FLAGGED, CAMEL_MESSAGE_FOLDER_FLAGGED);
2941
2942                 camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2943         }
2944
2945         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2946
2947         return TRUE;
2948 }
2949
2950 /**
2951  * camel_folder_summary_add:
2952  * @summary: a #CamelFolderSummary object
2953  * @info: a #CamelMessageInfo
2954  *
2955  * Adds a new @info record to the summary.  If @info->uid is %NULL,
2956  * then a new uid is automatically re-assigned by calling
2957  * camel_folder_summary_next_uid_string().
2958  *
2959  * The @info record should have been generated by calling one of the
2960  * info_new_*() functions, as it will be free'd based on the summary
2961  * class.  And MUST NOT be allocated directly using malloc.
2962  **/
2963 void
2964 camel_folder_summary_add (CamelFolderSummary *summary,
2965                           CamelMessageInfo *info)
2966 {
2967         CamelMessageInfoBase *base_info;
2968
2969         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
2970
2971         if (info == NULL)
2972                 return;
2973
2974         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2975         if (!summary_assign_uid (summary, info)) {
2976                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2977                 return;
2978         }
2979
2980         base_info = (CamelMessageInfoBase *) info;
2981         folder_summary_update_counts_by_flags (summary, camel_message_info_flags (info), UPDATE_COUNTS_ADD);
2982         base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
2983         base_info->dirty = TRUE;
2984
2985         g_hash_table_insert (summary->priv->uids,
2986                 (gpointer) camel_pstring_strdup (camel_message_info_uid (info)),
2987                 GUINT_TO_POINTER (camel_message_info_flags (info)));
2988
2989         /* Summary always holds a ref for the loaded infos */
2990         g_hash_table_insert (summary->priv->loaded_infos, (gpointer) camel_message_info_uid (info), info);
2991
2992         camel_folder_summary_touch (summary);
2993
2994         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
2995 }
2996
2997 /**
2998  * camel_folder_summary_insert:
2999  *
3000  * Since: 2.24
3001  **/
3002 void
3003 camel_folder_summary_insert (CamelFolderSummary *summary,
3004                              CamelMessageInfo *info,
3005                              gboolean load)
3006 {
3007         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
3008
3009         if (info == NULL)
3010                 return;
3011
3012         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3013
3014         if (!load) {
3015                 CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
3016
3017                 folder_summary_update_counts_by_flags (summary, camel_message_info_flags (info), UPDATE_COUNTS_ADD);
3018                 base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
3019                 base_info->dirty = TRUE;
3020
3021                 g_hash_table_insert (summary->priv->uids,
3022                         (gpointer) camel_pstring_strdup (camel_message_info_uid (info)),
3023                         GUINT_TO_POINTER (camel_message_info_flags (info)));
3024
3025                 camel_folder_summary_touch (summary);
3026         }
3027
3028         /* Summary always holds a ref for the loaded infos */
3029         g_hash_table_insert (summary->priv->loaded_infos, (gchar *) camel_message_info_uid (info), info);
3030
3031         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3032 }
3033
3034 /**
3035  * camel_folder_summary_add_from_header:
3036  * @summary: a #CamelFolderSummary object
3037  * @headers: rfc822 headers
3038  *
3039  * Build a new info record based on a set of headers, and add it to
3040  * the summary.
3041  *
3042  * Note that this function should not be used if build_content_info
3043  * has been specified for this summary.
3044  *
3045  * Returns: the newly added record
3046  **/
3047 CamelMessageInfo *
3048 camel_folder_summary_add_from_header (CamelFolderSummary *summary,
3049                                       struct _camel_header_raw *h)
3050 {
3051         CamelMessageInfo *info;
3052
3053         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
3054
3055         info = camel_folder_summary_info_new_from_header (summary, h);
3056
3057         camel_folder_summary_add (summary, info);
3058
3059         return info;
3060 }
3061
3062 /**
3063  * camel_folder_summary_add_from_parser:
3064  * @summary: a #CamelFolderSummary object
3065  * @parser: a #CamelMimeParser object
3066  *
3067  * Build a new info record based on the current position of a #CamelMimeParser.
3068  *
3069  * The parser should be positioned before the start of the message to summarise.
3070  * This function may be used if build_contnet_info or an index has been
3071  * specified for the summary.
3072  *
3073  * Returns: the newly added record
3074  **/
3075 CamelMessageInfo *
3076 camel_folder_summary_add_from_parser (CamelFolderSummary *summary,
3077                                       CamelMimeParser *mp)
3078 {
3079         CamelMessageInfo *info;
3080
3081         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
3082         g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), NULL);
3083
3084         info = camel_folder_summary_info_new_from_parser (summary, mp);
3085
3086         camel_folder_summary_add (summary, info);
3087
3088         return info;
3089 }
3090
3091 /**
3092  * camel_folder_summary_add_from_message:
3093  * @summary: a #CamelFolderSummary object
3094  * @message: a #CamelMimeMessage object
3095  *
3096  * Add a summary item from an existing message.
3097  *
3098  * Returns: the newly added record
3099  **/
3100 CamelMessageInfo *
3101 camel_folder_summary_add_from_message (CamelFolderSummary *summary,
3102                                        CamelMimeMessage *msg)
3103 {
3104         CamelMessageInfo *info = camel_folder_summary_info_new_from_message (summary, msg, NULL);
3105
3106         camel_folder_summary_add (summary, info);
3107
3108         return info;
3109 }
3110
3111 /**
3112  * camel_folder_summary_info_new_from_header:
3113  * @summary: a #CamelFolderSummary object
3114  * @headers: rfc822 headers
3115  *
3116  * Create a new info record from a header.
3117  *
3118  * Returns: the newly allocated record which must be freed with
3119  * camel_message_info_free()
3120  **/
3121 CamelMessageInfo *
3122 camel_folder_summary_info_new_from_header (CamelFolderSummary *summary,
3123                                            struct _camel_header_raw *h)
3124 {
3125         CamelFolderSummaryClass *class;
3126
3127         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
3128
3129         class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
3130         g_return_val_if_fail (class->message_info_new_from_header != NULL, NULL);
3131
3132         return class->message_info_new_from_header (summary, h);
3133 }
3134
3135 /**
3136  * camel_folder_summary_info_new_from_parser:
3137  * @summary: a #CamelFolderSummary object
3138  * @parser: a #CamelMimeParser object
3139  *
3140  * Create a new info record from a parser.  If the parser cannot
3141  * determine a uid, then none will be assigned.
3142  *
3143  * If indexing is enabled, and the parser cannot determine a new uid, then
3144  * one is automatically assigned.
3145  *
3146  * If indexing is enabled, then the content will be indexed based
3147  * on this new uid.  In this case, the message info MUST be
3148  * added using :add().
3149  *
3150  * Once complete, the parser will be positioned at the end of
3151  * the message.
3152  *
3153  * Returns: the newly allocated record which must be freed with
3154  * camel_message_info_free()
3155  **/
3156 CamelMessageInfo *
3157 camel_folder_summary_info_new_from_parser (CamelFolderSummary *summary,
3158                                            CamelMimeParser *mp)
3159 {
3160         CamelMessageInfo *info = NULL;
3161         gchar *buffer;
3162         gsize len;
3163         CamelFolderSummaryPrivate *p = summary->priv;
3164         goffset start;
3165         CamelIndexName *name = NULL;
3166
3167         /* should this check the parser is in the right state, or assume it is?? */
3168
3169         start = camel_mime_parser_tell (mp);
3170         if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_EOF) {
3171                 info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_parser (summary, mp);
3172
3173                 camel_mime_parser_unstep (mp);
3174
3175                 /* assign a unique uid, this is slightly 'wrong' as we do not really
3176                  * know if we are going to store this in the summary, but no matter */
3177                 if (p->index)
3178                         summary_assign_uid (summary, info);
3179
3180                 camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_FILTER_LOCK);
3181
3182                 if (p->index) {
3183                         if (p->filter_index == NULL)
3184                                 p->filter_index = camel_mime_filter_index_new (p->index);
3185                         camel_index_delete_name (p->index, camel_message_info_uid (info));
3186                         name = camel_index_add_name (p->index, camel_message_info_uid (info));
3187                         camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
3188                 }
3189
3190                 /* always scan the content info, even if we dont save it */
3191                 ((CamelMessageInfoBase *) info)->content = summary_build_content_info (summary, info, mp);
3192
3193                 if (name && p->index) {
3194                         camel_index_write_name (p->index, name);
3195                         g_object_unref (name);
3196                         camel_mime_filter_index_set_name (
3197                                 CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
3198                 }
3199
3200                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_FILTER_LOCK);
3201
3202                 ((CamelMessageInfoBase *) info)->size = camel_mime_parser_tell (mp) - start;
3203         }
3204         return info;
3205 }
3206
3207 /**
3208  * camel_folder_summary_info_new_from_message:
3209  * @summary: a #CamelFodlerSummary object
3210  * @message: a #CamelMimeMessage object
3211  * @bodystructure: a bodystructure or NULL
3212  *
3213  * Create a summary item from a message.
3214  *
3215  * Returns: the newly allocated record which must be freed using
3216  * camel_message_info_free()
3217  **/
3218 CamelMessageInfo *
3219 camel_folder_summary_info_new_from_message (CamelFolderSummary *summary,
3220                                             CamelMimeMessage *msg,
3221                                             const gchar *bodystructure)
3222 {
3223         CamelMessageInfo *info;
3224         CamelFolderSummaryPrivate *p = summary->priv;
3225         CamelIndexName *name = NULL;
3226
3227         info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_message (summary, msg, bodystructure);
3228
3229         /* assign a unique uid, this is slightly 'wrong' as we do not really
3230          * know if we are going to store this in the summary, but we need it set for indexing */
3231         if (p->index)
3232                 summary_assign_uid (summary, info);
3233
3234         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_FILTER_LOCK);
3235
3236         if (p->index) {
3237                 if (p->filter_index == NULL)
3238                         p->filter_index = camel_mime_filter_index_new (p->index);
3239                 camel_index_delete_name (p->index, camel_message_info_uid (info));
3240                 name = camel_index_add_name (p->index, camel_message_info_uid (info));
3241                 camel_mime_filter_index_set_name (
3242                         CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
3243
3244                 if (p->filter_stream == NULL) {
3245                         CamelStream *null = camel_stream_null_new ();
3246
3247                         p->filter_stream = camel_stream_filter_new (null);
3248                         g_object_unref (null);
3249                 }
3250         }
3251
3252         ((CamelMessageInfoBase *) info)->content = summary_build_content_info_message (summary, info, (CamelMimePart *) msg);
3253
3254         if (name) {
3255                 camel_index_write_name (p->index, name);
3256                 g_object_unref (name);
3257                 camel_mime_filter_index_set_name (
3258                         CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
3259         }
3260
3261         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_FILTER_LOCK);
3262
3263         return info;
3264 }
3265
3266 /**
3267  * camel_folder_summary_content_info_free:
3268  * @summary: a #CamelFolderSummary object
3269  * @ci: a #CamelMessageContentInfo
3270  *
3271  * Free the content info @ci, and all associated memory.
3272  **/
3273 void
3274 camel_folder_summary_content_info_free (CamelFolderSummary *summary,
3275                                         CamelMessageContentInfo *ci)
3276 {
3277         CamelMessageContentInfo *pw, *pn;
3278
3279         pw = ci->childs;
3280         CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_free (summary, ci);
3281         while (pw) {
3282                 pn = pw->next;
3283                 camel_folder_summary_content_info_free (summary, pw);
3284                 pw = pn;
3285         }
3286 }
3287
3288 /**
3289  * camel_folder_summary_touch:
3290  * @summary: a #CamelFolderSummary object
3291  *
3292  * Mark the summary as changed, so that a save will force it to be
3293  * written back to disk.
3294  **/
3295 void
3296 camel_folder_summary_touch (CamelFolderSummary *summary)
3297 {
3298         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3299         summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
3300         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3301 }
3302
3303 /**
3304  * camel_folder_summary_clear:
3305  * @summary: a #CamelFolderSummary object
3306  *
3307  * Empty the summary contents.
3308  **/
3309 gboolean
3310 camel_folder_summary_clear (CamelFolderSummary *summary,
3311                             GError **error)
3312 {
3313         GObject *summary_object;
3314         CamelStore *parent_store;
3315         CamelDB *cdb;
3316         const gchar *folder_name;
3317         gboolean res;
3318
3319         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
3320
3321         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3322         if (camel_folder_summary_count (summary) == 0) {
3323                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3324                 return TRUE;
3325         }
3326
3327         g_hash_table_remove_all (summary->priv->uids);
3328         remove_all_loaded (summary);
3329         g_hash_table_remove_all (summary->priv->loaded_infos);
3330
3331         summary->priv->saved_count = 0;
3332         summary->priv->unread_count = 0;
3333         summary->priv->deleted_count = 0;
3334         summary->priv->junk_count = 0;
3335         summary->priv->junk_not_deleted_count = 0;
3336         summary->priv->visible_count = 0;
3337
3338         camel_folder_summary_touch (summary);
3339
3340         folder_name = camel_folder_get_full_name (summary->priv->folder);
3341         parent_store = camel_folder_get_parent_store (summary->priv->folder);
3342         cdb = parent_store->cdb_w;
3343
3344         if (!is_in_memory_summary (summary))
3345                 res = camel_db_clear_folder_summary (cdb, folder_name, error) == 0;
3346         else
3347                 res = TRUE;
3348
3349         summary_object = G_OBJECT (summary);
3350         g_object_freeze_notify (summary_object);
3351         g_object_notify (summary_object, "saved-count");
3352         g_object_notify (summary_object, "unread-count");
3353         g_object_notify (summary_object, "deleted-count");
3354         g_object_notify (summary_object, "junk-count");
3355         g_object_notify (summary_object, "junk-not-deleted-count");
3356         g_object_notify (summary_object, "visible-count");
3357         g_object_thaw_notify (summary_object);
3358
3359         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3360
3361         return res;
3362 }
3363
3364 /**
3365  * camel_folder_summary_remove:
3366  * @summary: a #CamelFolderSummary object
3367  * @info: a #CamelMessageInfo
3368  *
3369  * Remove a specific @info record from the summary.
3370  *
3371  * Returns: Whether the @info was found and removed from the @summary.
3372  **/
3373 gboolean
3374 camel_folder_summary_remove (CamelFolderSummary *summary,
3375                              CamelMessageInfo *info)
3376 {
3377         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
3378         g_return_val_if_fail (info != NULL, FALSE);
3379
3380         if (camel_folder_summary_remove_uid (summary, camel_message_info_uid (info))) {
3381                 camel_message_info_free (info);
3382                 return TRUE;
3383         }
3384
3385         return FALSE;
3386 }
3387
3388 /**
3389  * camel_folder_summary_remove_uid:
3390  * @summary: a #CamelFolderSummary object
3391  * @uid: a uid
3392  *
3393  * Remove a specific info record from the summary, by @uid.
3394  *
3395  * Returns: Whether the @uid was found and removed from the @summary.
3396  **/
3397 gboolean
3398 camel_folder_summary_remove_uid (CamelFolderSummary *summary,
3399                                  const gchar *uid)
3400 {
3401         gpointer ptr_uid = NULL, ptr_flags = NULL;
3402         CamelStore *parent_store;
3403         const gchar *full_name;
3404         const gchar *uid_copy;
3405         gboolean res = TRUE;
3406
3407         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
3408         g_return_val_if_fail (uid != NULL, FALSE);
3409
3410         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3411         if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
3412                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3413                 return FALSE;
3414         }
3415
3416         folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
3417
3418         uid_copy = camel_pstring_strdup (uid);
3419         g_hash_table_remove (summary->priv->uids, uid_copy);
3420         g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
3421
3422         if (!is_in_memory_summary (summary)) {
3423                 full_name = camel_folder_get_full_name (summary->priv->folder);
3424                 parent_store = camel_folder_get_parent_store (summary->priv->folder);
3425                 if (camel_db_delete_uid (parent_store->cdb_w, full_name, uid_copy, NULL) != 0)
3426                         res = FALSE;
3427         }
3428
3429         camel_pstring_free (uid_copy);
3430
3431         camel_folder_summary_touch (summary);
3432         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3433
3434         return res;
3435 }
3436
3437 /**
3438  * camel_folder_summary_remove_uids:
3439  * @summary: a #CamelFolderSummary object
3440  * @uids: a GList of uids
3441  *
3442  * Remove a specific info record from the summary, by @uid.
3443  *
3444  * Returns: Whether the @uid was found and removed from the @summary.
3445  *
3446  * Since: 3.6
3447  **/
3448 gboolean
3449 camel_folder_summary_remove_uids (CamelFolderSummary *summary,
3450                                   GList *uids)
3451 {
3452         CamelStore *parent_store;
3453         const gchar *full_name;
3454         GList *l;
3455         gboolean res = TRUE;
3456
3457         g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
3458         g_return_val_if_fail (uids != NULL, FALSE);
3459
3460         g_object_freeze_notify (G_OBJECT (summary));
3461         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3462
3463         for (l = g_list_first (uids); l; l = g_list_next (l)) {
3464                 gpointer ptr_uid = NULL, ptr_flags = NULL;
3465                 if (g_hash_table_lookup_extended (summary->priv->uids, l->data, &ptr_uid, &ptr_flags)) {
3466                         const gchar *uid_copy = camel_pstring_strdup (l->data);
3467                         CamelMessageInfo *mi;
3468
3469                         folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
3470                         g_hash_table_remove (summary->priv->uids, uid_copy);
3471
3472                         mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
3473                         g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
3474
3475                         if (mi)
3476                                 camel_message_info_free (mi);
3477                         camel_pstring_free (uid_copy);
3478                 }
3479         }
3480
3481         if (is_in_memory_summary (summary)) {
3482                 full_name = camel_folder_get_full_name (summary->priv->folder);
3483                 parent_store = camel_folder_get_parent_store (summary->priv->folder);
3484                 if (camel_db_delete_uids (parent_store->cdb_w, full_name, uids, NULL) != 0)
3485                         res = FALSE;
3486         }
3487
3488         camel_folder_summary_touch (summary);
3489         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3490         g_object_thaw_notify (G_OBJECT (summary));
3491
3492         return res;
3493 }
3494
3495 static struct _node *
3496 my_list_append (struct _node **list,
3497                 struct _node *n)
3498 {
3499         struct _node *ln = *list;
3500         n->next = NULL;
3501
3502         if (!ln) {
3503                 *list = n;
3504                 return n;
3505         }
3506
3507         while (ln->next)
3508                 ln = ln->next;
3509         ln->next = n;
3510         return n;
3511 }
3512
3513 static gint
3514 my_list_size (struct _node **list)
3515 {
3516         gint len = 0;
3517         struct _node *ln = (struct _node *) list;
3518         while (ln->next) {
3519                 ln = ln->next;
3520                 len++;
3521         }
3522         return len;
3523 }
3524
3525 /* are these even useful for anything??? */
3526 static CamelMessageInfo *
3527 message_info_new_from_parser (CamelFolderSummary *summary,
3528                               CamelMimeParser *mp)
3529 {
3530         CamelMessageInfo *mi = NULL;
3531         gint state;
3532
3533         state = camel_mime_parser_state (mp);
3534         switch (state) {
3535         case CAMEL_MIME_PARSER_STATE_HEADER:
3536         case CAMEL_MIME_PARSER_STATE_MESSAGE:
3537         case CAMEL_MIME_PARSER_STATE_MULTIPART:
3538                 mi = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
3539                 break;
3540         default:
3541                 g_error ("Invalid parser state");
3542         }
3543
3544         return mi;
3545 }
3546
3547 static CamelMessageContentInfo *
3548 content_info_new_from_parser (CamelFolderSummary *summary,
3549                               CamelMimeParser *mp)
3550 {
3551         CamelMessageContentInfo *ci = NULL;
3552
3553         switch (camel_mime_parser_state (mp)) {
3554         case CAMEL_MIME_PARSER_STATE_HEADER:
3555         case CAMEL_MIME_PARSER_STATE_MESSAGE:
3556         case CAMEL_MIME_PARSER_STATE_MULTIPART:
3557                 ci = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
3558                 if (ci) {
3559                         if (ci->type)
3560                                 camel_content_type_unref (ci->type);
3561                         ci->type = camel_mime_parser_content_type (mp);
3562                         camel_content_type_ref (ci->type);
3563                 }
3564                 break;
3565         default:
3566                 g_error ("Invalid parser state");
3567         }
3568
3569         return ci;
3570 }
3571
3572 static CamelMessageInfo *
3573 message_info_new_from_message (CamelFolderSummary *summary,
3574                                CamelMimeMessage *msg,
3575                                const gchar *bodystructure)
3576 {
3577         CamelMessageInfo *mi;
3578
3579         mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->message_info_new_from_header (summary, ((CamelMimePart *) msg)->headers);
3580         ((CamelMessageInfoBase *) mi)->bodystructure = g_strdup (bodystructure);
3581
3582         return mi;
3583 }
3584
3585 static CamelMessageContentInfo *
3586 content_info_new_from_message (CamelFolderSummary *summary,
3587                                CamelMimePart *mp)
3588 {
3589         CamelMessageContentInfo *ci;
3590
3591         ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->content_info_new_from_header (summary, mp->headers);
3592
3593         return ci;
3594 }
3595
3596 static gchar *
3597 summary_format_address (struct _camel_header_raw *h,
3598                         const gchar *name,
3599                         const gchar *charset)
3600 {
3601         struct _camel_header_address *addr;
3602         gchar *text, *str;
3603
3604         if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
3605                 return NULL;
3606
3607         while (isspace ((unsigned) *text))
3608                 text++;
3609
3610         text = camel_header_unfold (text);
3611
3612         if ((addr = camel_header_address_decode (text, charset))) {
3613                 str = camel_header_address_list_format (addr);
3614                 camel_header_address_list_clear (&addr);
3615                 g_free (text);
3616         } else {
3617                 str = text;
3618         }
3619
3620         return str;
3621 }
3622
3623 static gchar *
3624 summary_format_string (struct _camel_header_raw *h,
3625                        const gchar *name,
3626                        const gchar *charset)
3627 {
3628         gchar *text, *str;
3629
3630         if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
3631                 return NULL;
3632
3633         while (isspace ((unsigned) *text))
3634                 text++;
3635
3636         text = camel_header_unfold (text);
3637         str = camel_header_decode_string (text, charset);
3638         g_free (text);
3639
3640         return str;
3641 }
3642
3643 /**
3644  * camel_folder_summary_content_info_new:
3645  * @summary: a #CamelFolderSummary object
3646  *
3647  * Allocate a new #CamelMessageContentInfo, suitable for adding
3648  * to this summary.
3649  *
3650  * Returns: a newly allocated #CamelMessageContentInfo
3651  **/
3652 CamelMessageContentInfo *
3653 camel_folder_summary_content_info_new (CamelFolderSummary *summary)
3654 {
3655         CamelFolderSummaryClass *class;
3656         CamelMessageContentInfo *ci;
3657
3658         class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
3659
3660         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_ALLOC_LOCK);
3661         ci = g_slice_alloc0 (class->content_info_size);
3662         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_ALLOC_LOCK);
3663
3664         return ci;
3665 }
3666
3667 static CamelMessageInfo *
3668 message_info_new_from_header (CamelFolderSummary *summary,
3669                               struct _camel_header_raw *h)
3670 {
3671         const gchar *received, *date, *content, *charset = NULL;
3672         struct _camel_header_references *refs, *irt, *scan;
3673         gchar *subject, *from, *to, *cc, *mlist;
3674         CamelContentType *ct = NULL;
3675         CamelMessageInfoBase *mi;
3676         guint8 *digest;
3677         gsize length;
3678         gchar *msgid;
3679         gint count;
3680
3681         length = g_checksum_type_get_length (G_CHECKSUM_MD5);
3682         digest = g_alloca (length);
3683
3684         mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
3685
3686         if ((content = camel_header_raw_find (&h, "Content-Type", NULL))
3687              && (ct = camel_content_type_decode (content))
3688              && (charset = camel_content_type_param (ct, "charset"))
3689              && (g_ascii_strcasecmp (charset, "us-ascii") == 0))
3690                 charset = NULL;
3691
3692         charset = charset ? camel_iconv_charset_name (charset) : NULL;
3693
3694         subject = summary_format_string (h, "subject", charset);
3695         from = summary_format_address (h, "from", charset);
3696         to = summary_format_address (h, "to", charset);
3697         cc = summary_format_address (h, "cc", charset);
3698         mlist = camel_header_raw_check_mailing_list (&h);
3699
3700         if (ct)
3701                 camel_content_type_unref (ct);
3702
3703         mi->subject = camel_pstring_add (subject, TRUE);
3704         mi->from = camel_pstring_add (from, TRUE);
3705         mi->to = camel_pstring_add (to, TRUE);
3706         mi->cc = camel_pstring_add (cc, TRUE);
3707         mi->mlist = camel_pstring_add (mlist, TRUE);
3708
3709         mi->user_flags = NULL;
3710         mi->user_tags = NULL;
3711
3712         if ((date = camel_header_raw_find (&h, "date", NULL)))
3713                 mi->date_sent = camel_header_decode_date (date, NULL);
3714         else
3715                 mi->date_sent = 0;
3716
3717         received = camel_header_raw_find (&h, "received", NULL);
3718         if (received)
3719                 received = strrchr (received, ';');
3720         if (received)
3721                 mi->date_received = camel_header_decode_date (received + 1, NULL);
3722         else
3723                 mi->date_received = 0;
3724
3725         msgid = camel_header_msgid_decode (camel_header_raw_find (&h, "message-id", NULL));
3726         if (msgid) {
3727                 GChecksum *checksum;
3728
3729                 checksum = g_checksum_new (G_CHECKSUM_MD5);
3730                 g_checksum_update (checksum, (guchar *) msgid, -1);
3731                 g_checksum_get_digest (checksum, digest, &length);
3732                 g_checksum_free (checksum);
3733
3734                 memcpy (mi->message_id.id.hash, digest, sizeof (mi->message_id.id.hash));
3735                 g_free (msgid);
3736         }
3737
3738         /* decode our references and in-reply-to headers */
3739         refs = camel_header_references_decode (camel_header_raw_find (&h, "references", NULL));
3740         irt = camel_header_references_inreplyto_decode (camel_header_raw_find (&h, "in-reply-to", NULL));
3741         if (refs || irt) {
3742                 if (irt) {
3743                         /* The References field is populated from the "References" and/or "In-Reply-To"
3744                          * headers. If both headers exist, take the first thing in the In-Reply-To header
3745                          * that looks like a Message-ID, and append it to the References header. */
3746
3747                         if (refs)
3748                                 irt->next = refs;
3749
3750                         refs = irt;
3751                 }
3752
3753                 count = camel_header_references_list_size (&refs);
3754                 mi->references = g_malloc (sizeof (*mi->references) + ((count - 1) * sizeof (mi->references->references[0])));
3755                 count = 0;
3756                 scan = refs;
3757                 while (scan) {
3758                         GChecksum *checksum;
3759
3760                         checksum = g_checksum_new (G_CHECKSUM_MD5);
3761                         g_checksum_update (checksum, (guchar *) scan->id, -1);
3762                         g_checksum_get_digest (checksum, digest, &length);
3763                         g_checksum_free (checksum);
3764
3765                         memcpy (mi->references->references[count].id.hash, digest, sizeof (mi->message_id.id.hash));
3766                         count++;
3767                         scan = scan->next;
3768                 }
3769                 mi->references->size = count;
3770                 camel_header_references_list_clear (&refs);
3771         }
3772
3773         return (CamelMessageInfo *) mi;
3774 }
3775
3776 static void
3777 message_info_free (CamelFolderSummary *summary,
3778                    CamelMessageInfo *info)
3779 {
3780         CamelFolderSummaryClass *class;
3781         CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
3782
3783         if (mi->uid) {
3784                 if (summary) {
3785                         camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3786                         if (g_hash_table_lookup (summary->priv->loaded_infos, mi->uid) == mi) {
3787                                 g_hash_table_remove (summary->priv->loaded_infos, mi->uid);
3788                         }
3789                         camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);
3790                 }
3791                 camel_pstring_free (mi->uid);
3792         }
3793         camel_pstring_free (mi->subject);
3794         camel_pstring_free (mi->from);
3795         camel_pstring_free (mi->to);
3796         camel_pstring_free (mi->cc);
3797         camel_pstring_free (mi->mlist);
3798         g_free (mi->bodystructure);
3799         g_free (mi->references);
3800         g_free (mi->preview);
3801         camel_flag_list_free (&mi->user_flags);
3802         camel_tag_list_free (&mi->user_tags);
3803         if (mi->headers)
3804                 camel_header_param_list_free (mi->headers);
3805
3806         if (summary) {
3807                 class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
3808                 g_slice_free1 (class->message_info_size, mi);
3809         } else
3810                 g_slice_free (CamelMessageInfoBase, mi);
3811 }
3812
3813 static CamelMessageContentInfo *
3814 content_info_new_from_header (CamelFolderSummary *summary,
3815                               struct _camel_header_raw *h)
3816 {
3817         CamelMessageContentInfo *ci;
3818         const gchar *charset;
3819
3820         ci = camel_folder_summary_content_info_new (summary);
3821
3822         charset = camel_iconv_locale_charset ();
3823         ci->id = camel_header_msgid_decode (camel_header_raw_find (&h, "content-id", NULL));
3824         ci->description = camel_header_decode_string (camel_header_raw_find (&h, "content-description", NULL), charset);
3825         ci->encoding = camel_content_transfer_encoding_decode (camel_header_raw_find (&h, "content-transfer-encoding", NULL));
3826         ci->type = camel_content_type_decode (camel_header_raw_find (&h, "content-type", NULL));
3827
3828         return ci;
3829 }
3830
3831 static void
3832 content_info_free (CamelFolderSummary *summary,
3833                    CamelMessageContentInfo *ci)
3834 {
3835         CamelFolderSummaryClass *class;
3836
3837         class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
3838
3839         camel_content_type_unref (ci->type);
3840         g_free (ci->id);
3841         g_free (ci->description);
3842         g_free (ci->encoding);
3843         g_slice_free1 (class->content_info_size, ci);
3844 }
3845
3846 static gchar *
3847 next_uid_string (CamelFolderSummary *summary)
3848 {
3849         return g_strdup_printf ("%u", camel_folder_summary_next_uid (summary));
3850 }
3851
3852 /*
3853   OK
3854   Now this is where all the "smarts" happen, where the content info is built,
3855   and any indexing and what not is performed
3856 */
3857
3858 /* must have filter_lock before calling this function */
3859 static CamelMessageContentInfo *
3860 summary_build_content_info (CamelFolderSummary *summary,
3861                             CamelMessageInfo *msginfo,
3862                             CamelMimeParser *mp)
3863 {
3864         gint state;
3865         gsize len;
3866         gchar *buffer;
3867         CamelMessageContentInfo *info = NULL;
3868         CamelContentType *ct;
3869         gint enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
3870         CamelFolderSummaryPrivate *p = summary->priv;
3871         CamelMimeFilter *mfc;
3872         CamelMessageContentInfo *part;
3873         const gchar *calendar_header;
3874
3875         d (printf ("building content info\n"));
3876
3877         /* start of this part */
3878         state = camel_mime_parser_step (mp, &buffer, &len);
3879
3880         if (summary->priv->build_content)
3881                 info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_parser (summary, mp);
3882
3883         switch (state) {
3884         case CAMEL_MIME_PARSER_STATE_HEADER:
3885                 /* check content type for indexing, then read body */
3886                 ct = camel_mime_parser_content_type (mp);
3887                 /* update attachments flag as we go */
3888                 if (camel_content_type_is (ct, "application", "pgp-signature")
3889 #ifdef ENABLE_SMIME
3890                     || camel_content_type_is (ct, "application", "x-pkcs7-signature")
3891                     || camel_content_type_is (ct, "application", "pkcs7-signature")
3892 #endif
3893                         )
3894                         camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3895
3896                 calendar_header = camel_mime_parser_header (mp, "Content-class", NULL);
3897                 if (calendar_header && g_ascii_strcasecmp (calendar_header, "urn:content-classes:calendarmessage") != 0)
3898                         calendar_header = NULL;
3899
3900                 if (!calendar_header)
3901                         calendar_header = camel_mime_parser_header (mp, "X-Calendar-Attachment", NULL);
3902
3903                 if (calendar_header || camel_content_type_is (ct, "text", "calendar"))
3904                         camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
3905
3906                 if (p->index && camel_content_type_is (ct, "text", "*")) {
3907                         gchar *encoding;
3908                         const gchar *charset;
3909
3910                         d (printf ("generating index:\n"));
3911
3912                         encoding = camel_content_transfer_encoding_decode (camel_mime_parser_header (mp, "content-transfer-encoding", NULL));
3913                         if (encoding) {
3914                                 if (!g_ascii_strcasecmp (encoding, "base64")) {
3915                                         d (printf (" decoding base64\n"));
3916                                         if (p->filter_64 == NULL)
3917                                                 p->filter_64 = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
3918                                         else
3919                                                 camel_mime_filter_reset (p->filter_64);
3920                                         enc_id = camel_mime_parser_filter_add (mp, p->filter_64);
3921                                 } else if (!g_ascii_strcasecmp (encoding, "quoted-printable")) {
3922                                         d (printf (" decoding quoted-printable\n"));
3923                                         if (p->filter_qp == NULL)
3924                                                 p->filter_qp = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_DEC);
3925                                         else
3926                                                 camel_mime_filter_reset (p->filter_qp);
3927                                         enc_id = camel_mime_parser_filter_add (mp, p->filter_qp);
3928                                 } else if (!g_ascii_strcasecmp (encoding, "x-uuencode")) {
3929                                         d (printf (" decoding x-uuencode\n"));
3930                                         if (p->filter_uu == NULL)
3931                                                 p->filter_uu = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
3932                                         else
3933                                                 camel_mime_filter_reset (p->filter_uu);
3934                                         enc_id = camel_mime_parser_filter_add (mp, p->filter_uu);
3935                                 } else {
3936                                         d (printf (" ignoring encoding %s\n", encoding));
3937                                 }
3938                                 g_free (encoding);
3939                         }
3940
3941                         charset = camel_content_type_param (ct, "charset");
3942                         if (charset != NULL
3943                             && !(g_ascii_strcasecmp (charset, "us-ascii") == 0
3944                                  || g_ascii_strcasecmp (charset, "utf-8") == 0)) {
3945                                 d (printf (" Adding conversion filter from %s to UTF-8\n", charset));
3946                                 mfc = g_hash_table_lookup (p->filter_charset, charset);
3947                                 if (mfc == NULL) {
3948                                         mfc = camel_mime_filter_charset_new (charset, "UTF-8");
3949                                         if (mfc)
3950                                                 g_hash_table_insert (p->filter_charset, g_strdup (charset), mfc);
3951                                 } else {
3952                                         camel_mime_filter_reset ((CamelMimeFilter *) mfc);
3953                                 }
3954                                 if (mfc) {
3955                                         chr_id = camel_mime_parser_filter_add (mp, mfc);
3956                                 } else {
3957                                         w (g_warning ("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset));
3958                                 }
3959                         }
3960
3961                         /* we do charset conversions before this filter, which isn't strictly correct,
3962                          * but works in most cases */
3963                         if (camel_content_type_is (ct, "text", "html")) {
3964                                 if (p->filter_html == NULL)
3965                                         p->filter_html = camel_mime_filter_html_new ();
3966                                 else
3967                                         camel_mime_filter_reset ((CamelMimeFilter *) p->filter_html);
3968                                 html_id = camel_mime_parser_filter_add (mp, (CamelMimeFilter *) p->filter_html);
3969                         }
3970
3971                         /* and this filter actually does the indexing */
3972                         idx_id = camel_mime_parser_filter_add (mp, p->filter_index);
3973                 }
3974                 /* and scan/index everything */
3975                 while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
3976                         ;
3977                 /* and remove the filters */
3978                 camel_mime_parser_filter_remove (mp, enc_id);
3979                 camel_mime_parser_filter_remove (mp, chr_id);
3980                 camel_mime_parser_filter_remove (mp, html_id);
3981                 camel_mime_parser_filter_remove (mp, idx_id);
3982                 break;
3983         case CAMEL_MIME_PARSER_STATE_MULTIPART:
3984                 d (printf ("Summarising multipart\n"));
3985                 /* update attachments flag as we go */
3986                 ct = camel_mime_parser_content_type (mp);
3987                 if (camel_content_type_is (ct, "multipart", "mixed"))
3988                         camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
3989                 if (camel_content_type_is (ct, "multipart", "signed")
3990                     || camel_content_type_is (ct, "multipart", "encrypted"))
3991                         camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3992
3993                 while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
3994                         camel_mime_parser_unstep (mp);
3995                         part = summary_build_content_info (summary, msginfo, mp);
3996                         if (part) {
3997                                 part->parent = info;
3998                                 my_list_append ((struct _node **) &info->childs, (struct _node *) part);
3999                         }
4000                 }
4001                 break;
4002         case CAMEL_MIME_PARSER_STATE_MESSAGE:
4003                 d (printf ("Summarising message\n"));
4004                 /* update attachments flag as we go */
4005                 camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
4006
4007                 part = summary_build_content_info (summary, msginfo, mp);
4008                 if (part) {
4009                         part->parent = info;
4010                         my_list_append ((struct _node **) &info->childs, (struct _node *) part);
4011                 }
4012                 state = camel_mime_parser_step (mp, &buffer, &len);
4013                 if (state != CAMEL_MIME_PARSER_STATE_MESSAGE_END) {
4014                         g_error ("Bad parser state: Expecing MESSAGE_END or MESSAGE_EOF, got: %d", state);
4015                         camel_mime_parser_unstep (mp);
4016                 }
4017                 break;
4018         }
4019
4020         d (printf ("finished building content info\n"));
4021
4022         return info;
4023 }
4024
4025 /* build the content-info, from a message */
4026 /* this needs the filter lock since it uses filters to perform indexing */
4027 static CamelMessageContentInfo *
4028 summary_build_content_info_message (CamelFolderSummary *summary,
4029                                     CamelMessageInfo *msginfo,
4030                                     CamelMimePart *object)
4031 {
4032         CamelDataWrapper *containee;
4033         gint parts, i;
4034         CamelFolderSummaryPrivate *p = summary->priv;
4035         CamelMessageContentInfo *info = NULL, *child;
4036         CamelContentType *ct;
4037         const struct _camel_header_raw *header;
4038
4039         if (summary->priv->build_content)
4040                 info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_message (summary, object);
4041
4042         containee = camel_medium_get_content (CAMEL_MEDIUM (object));
4043
4044         if (containee == NULL)
4045                 return info;
4046
4047         /* TODO: I find it odd that get_part and get_content do not
4048          * add a reference, probably need fixing for multithreading */
4049
4050         /* check for attachments */
4051         ct = ((CamelDataWrapper *) containee)->mime_type;
4052         if (camel_content_type_is (ct, "multipart", "*")) {
4053                 if (camel_content_type_is (ct, "multipart", "mixed"))
4054                         camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
4055                 if (camel_content_type_is (ct, "multipart", "signed")
4056                     || camel_content_type_is (ct, "multipart", "encrypted"))
4057                         camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
4058         } else if (camel_content_type_is (ct, "application", "pgp-signature")
4059 #ifdef ENABLE_SMIME
4060                     || camel_content_type_is (ct, "application", "x-pkcs7-signature")
4061                     || camel_content_type_is (ct, "application", "pkcs7-signature")
4062 #endif
4063                 ) {
4064                 camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
4065         }
4066
4067         for (header = object->headers; header; header = header->next) {
4068                 const gchar *value = header->value;
4069
4070                 /* skip preceding spaces in the value */
4071                 while (value && *value && isspace (*value))
4072                         value++;
4073
4074                 if (header->name && value && (
4075                     (g_ascii_strcasecmp (header->name, "Content-class") == 0 && g_ascii_strcasecmp (value, "urn:content-classes:calendarmessage") == 0) ||
4076                     (g_ascii_strcasecmp (header->name, "X-Calendar-Attachment") == 0)))
4077                         break;
4078         }
4079
4080         if (header || camel_content_type_is (ct, "text", "calendar"))
4081                 camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
4082
4083         /* using the object types is more accurate than using the mime/types */
4084         if (CAMEL_IS_MULTIPART (containee)) {
4085                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
4086
4087                 for (i = 0; i < parts; i++) {
4088                         CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
4089                         g_assert (part);
4090                         child = summary_build_content_info_message (summary, msginfo, part);
4091                         if (child) {
4092                                 child->parent = info;
4093                                 my_list_append ((struct _node **) &info->childs, (struct _node *) child);
4094                         }
4095                 }
4096         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
4097                 /* for messages we only look at its contents */
4098                 child = summary_build_content_info_message (summary, msginfo, (CamelMimePart *) containee);
4099                 if (child) {
4100                         child->parent = info;
4101                         my_list_append ((struct _node **) &info->childs, (struct _node *) child);
4102                 }
4103         } else if (p->filter_stream
4104                    && camel_content_type_is (ct, "text", "*")) {
4105                 gint html_id = -1, idx_id = -1;
4106
4107                 /* pre-attach html filter if required, otherwise just index filter */
4108                 if (camel_content_type_is (ct, "text", "html")) {
4109                         if (p->filter_html == NULL)
4110                                 p->filter_html = camel_mime_filter_html_new ();
4111                         else
4112                                 camel_mime_filter_reset ((CamelMimeFilter *) p->filter_html);
4113                         html_id = camel_stream_filter_add (
4114                                 CAMEL_STREAM_FILTER (p->filter_stream),
4115                                 (CamelMimeFilter *) p->filter_html);
4116                 }
4117                 idx_id = camel_stream_filter_add (
4118                         CAMEL_STREAM_FILTER (p->filter_stream),
4119                         p->filter_index);
4120
4121                 /* FIXME Pass a GCancellable and GError here. */
4122                 camel_data_wrapper_decode_to_stream_sync (
4123                         containee, p->filter_stream, NULL, NULL);
4124                 camel_stream_flush (p->filter_stream, NULL, NULL);
4125
4126                 camel_stream_filter_remove (
4127                         CAMEL_STREAM_FILTER (p->filter_stream), idx_id);
4128                 camel_stream_filter_remove (
4129                         CAMEL_STREAM_FILTER (p->filter_stream), html_id);
4130         }
4131
4132         return info;
4133 }
4134
4135 /**
4136  * camel_flag_get:
4137  * @list: the address of a #CamelFlag list
4138  * @name: name of the flag to get
4139  *
4140  * Find the state of the flag @name in @list.
4141  *
4142  * Returns: the state of the flag (%TRUE or %FALSE)
4143  **/
4144 gboolean
4145 camel_flag_get (CamelFlag **list,
4146                 const gchar *name)
4147 {
4148         CamelFlag *flag;
4149         flag = *list;
4150         while (flag) {
4151                 if (!strcmp (flag->name, name))
4152                         return TRUE;
4153                 flag = flag->next;
4154         }
4155         return FALSE;
4156 }
4157
4158 /**
4159  * camel_flag_set:
4160  * @list: the address of a #CamelFlag list
4161  * @name: name of the flag to set or change
4162  * @value: the value to set on the flag
4163  *
4164  * Set the state of a flag @name in the list @list to @value.
4165  *
4166  * Returns: %TRUE if the value of the flag has been changed or %FALSE
4167  * otherwise
4168  **/
4169 gboolean
4170 camel_flag_set (CamelFlag **list,
4171                 const gchar *name,
4172                 gboolean value)
4173 {
4174         CamelFlag *flag, *tmp;
4175
4176         if (!name)
4177                 return TRUE;
4178
4179         /* this 'trick' works because flag->next is the first element */
4180         flag = (CamelFlag *) list;
4181         while (flag->next) {
4182                 tmp = flag->next;
4183                 if (!strcmp (flag->next->name, name)) {
4184                         if (!value) {
4185                                 flag->next = tmp->next;
4186                                 g_free (tmp);
4187                         }
4188                         return !value;
4189                 }
4190                 flag = tmp;
4191         }
4192
4193         if (value) {
4194                 tmp = g_malloc (sizeof (*tmp) + strlen (name));
4195                 strcpy (tmp->name, name);
4196                 tmp->next = NULL;
4197                 flag->next = tmp;
4198         }
4199         return value;
4200 }
4201
4202 /**
4203  * camel_flag_list_size:
4204  * @list: the address of a #CamelFlag list
4205  *
4206  * Get the length of the flag list.
4207  *
4208  * Returns: the number of flags in the list
4209  **/
4210 gint
4211 camel_flag_list_size (CamelFlag **list)
4212 {
4213         gint count = 0;
4214         CamelFlag *flag;
4215
4216         flag = *list;
4217         while (flag) {
4218                 count++;
4219                 flag = flag->next;
4220         }
4221         return count;
4222 }
4223
4224 /**
4225  * camel_flag_list_free:
4226  * @list: the address of a #CamelFlag list
4227  *
4228  * Free the memory associated with the flag list @list.
4229  **/
4230 void
4231 camel_flag_list_free (CamelFlag **list)
4232 {
4233         CamelFlag *flag, *tmp;
4234         flag = *list;
4235         while (flag) {
4236                 tmp = flag->next;
4237                 g_free (flag);
4238                 flag = tmp;
4239         }
4240         *list = NULL;
4241 }
4242
4243 /**
4244  * camel_flag_list_copy:
4245  * @to: the address of the #CamelFlag list to copy to
4246  * @from: the address of the #CamelFlag list to copy from
4247  *
4248  * Copy a flag list.
4249  *
4250  * Returns: %TRUE if @to is changed or %FALSE otherwise
4251  **/
4252 gboolean
4253 camel_flag_list_copy (CamelFlag **to,
4254                       CamelFlag **from)
4255 {
4256         CamelFlag *flag, *tmp;
4257         gint changed = FALSE;
4258
4259         if (*to == NULL && from == NULL)
4260                 return FALSE;
4261
4262         /* Remove any now-missing flags */
4263         flag = (CamelFlag *) to;
4264         while (flag->next) {
4265                 tmp = flag->next;
4266                 if (!camel_flag_get (from, tmp->name)) {
4267                         flag->next = tmp->next;
4268                         g_free (tmp);
4269                         changed = TRUE;
4270                 } else {
4271                         flag = tmp;
4272                 }
4273         }
4274
4275         /* Add any new flags */
4276         flag = *from;
4277         while (flag) {
4278                 changed |= camel_flag_set (to, flag->name, TRUE);
4279                 flag = flag->next;
4280         }
4281
4282         return changed;
4283 }
4284
4285 /**
4286  * camel_tag_get:
4287  * @list: the address of a #CamelTag list
4288  * @name: name of the tag to get
4289  *
4290  * Find the flag @name in @list and get the value.
4291  *
4292  * Returns: the value of the flag  or %NULL if unset
4293  **/
4294 const gchar *
4295 camel_tag_get (CamelTag **list,
4296                const gchar *name)
4297 {
4298         CamelTag *tag;
4299
4300         tag = *list;
4301         while (tag) {
4302                 if (!strcmp (tag->name, name))
4303                         return (const gchar *) tag->value;
4304                 tag = tag->next;
4305         }
4306         return NULL;
4307 }
4308
4309 /**
4310  * camel_tag_set:
4311  * @list: the address of a #CamelTag list
4312  * @name: name of the tag to set
4313  * @value: value to set on the tag
4314  *
4315  * Set the tag @name in the tag list @list to @value.
4316  *
4317  * Returns: %TRUE if the value on the tag changed or %FALSE otherwise
4318  **/
4319 gboolean
4320 camel_tag_set (CamelTag **list,
4321                const gchar *name,
4322                const gchar *value)
4323 {
4324         CamelTag *tag, *tmp;
4325
4326         /* this 'trick' works because tag->next is the first element */
4327         tag = (CamelTag *) list;
4328         while (tag->next) {
4329                 tmp = tag->next;
4330                 if (!strcmp (tmp->name, name)) {
4331                         if (value == NULL) { /* clear it? */
4332                                 tag->next = tmp->next;
4333                                 g_free (tmp->value);
4334                                 g_free (tmp);
4335                                 return TRUE;
4336                         } else if (strcmp (tmp->value, value)) { /* has it changed? */
4337                                 g_free (tmp->value);
4338                                 tmp->value = g_strdup (value);
4339                                 return TRUE;
4340                         }
4341                         return FALSE;
4342                 }
4343                 tag = tmp;
4344         }
4345
4346         if (value) {
4347                 tmp = g_malloc (sizeof (*tmp) + strlen (name));
4348                 strcpy (tmp->name, name);
4349                 tmp->value = g_strdup (value);
4350                 tmp->next = NULL;
4351                 tag->next = tmp;
4352                 return TRUE;
4353         }
4354         return FALSE;
4355 }
4356
4357 /**
4358  * camel_tag_list_size:
4359  * @list: the address of a #CamelTag list
4360  *
4361  * Get the number of tags present in the tag list @list.
4362  *
4363  * Returns: the number of tags
4364  **/
4365 gint
4366 camel_tag_list_size (CamelTag **list)
4367 {
4368         gint count = 0;
4369         CamelTag *tag;
4370
4371         tag = *list;
4372         while (tag) {
4373                 count++;
4374                 tag = tag->next;
4375         }
4376         return count;
4377 }
4378
4379 static void
4380 rem_tag (gchar *key,
4381          gchar *value,
4382          CamelTag **to)
4383 {
4384         camel_tag_set (to, key, NULL);
4385 }
4386
4387 /**
4388  * camel_tag_list_copy:
4389  * @to: the address of the #CamelTag list to copy to
4390  * @from: the address of the #CamelTag list to copy from
4391  *
4392  * Copy a tag list.
4393  *
4394  * Returns: %TRUE if @to is changed or %FALSE otherwise
4395  **/
4396 gboolean
4397 camel_tag_list_copy (CamelTag **to,
4398                      CamelTag **from)
4399 {
4400         gint changed = FALSE;
4401         CamelTag *tag;
4402         GHashTable *left;
4403
4404         if (*to == NULL && from == NULL)
4405                 return FALSE;
4406
4407         left = g_hash_table_new (g_str_hash, g_str_equal);
4408         tag = *to;
4409         while (tag) {
4410                 g_hash_table_insert (left, tag->name, tag);
4411                 tag = tag->next;
4412         }
4413
4414         tag = *from;
4415         while (tag) {
4416                 changed |= camel_tag_set (to, tag->name, tag->value);
4417                 g_hash_table_remove (left, tag->name);
4418                 tag = tag->next;
4419         }
4420
4421         if (g_hash_table_size (left) > 0) {
4422                 g_hash_table_foreach (left, (GHFunc) rem_tag, to);
4423                 changed = TRUE;
4424         }
4425         g_hash_table_destroy (left);
4426
4427         return changed;
4428 }
4429
4430 /**
4431  * camel_tag_list_free:
4432  * @list: the address of a #CamelTag list
4433  *
4434  * Free the tag list @list.
4435  **/
4436 void
4437 camel_tag_list_free (CamelTag **list)
4438 {
4439         CamelTag *tag, *tmp;
4440         tag = *list;
4441         while (tag) {
4442                 tmp = tag->next;
4443                 g_free (tag->value);
4444                 g_free (tag);
4445                 tag = tmp;
4446         }
4447         *list = NULL;
4448 }
4449
4450 static struct flag_names_t {
4451         const gchar *name;
4452         guint32 value;
4453 } flag_names[] = {
4454         { "answered", CAMEL_MESSAGE_ANSWERED },
4455         { "deleted", CAMEL_MESSAGE_DELETED },
4456         { "draft", CAMEL_MESSAGE_DRAFT },
4457         { "flagged", CAMEL_MESSAGE_FLAGGED },
4458         { "seen", CAMEL_MESSAGE_SEEN },
4459         { "attachments", CAMEL_MESSAGE_ATTACHMENTS },
4460         { "junk", CAMEL_MESSAGE_JUNK },
4461         { "notjunk", CAMEL_MESSAGE_NOTJUNK },
4462         { "secure", CAMEL_MESSAGE_SECURE },
4463         { NULL, 0 }
4464 };
4465
4466 /**
4467  * camel_system_flag:
4468  * @name: name of a system flag
4469  *
4470  * Returns: the integer value of the system flag string
4471  **/
4472 CamelMessageFlags
4473 camel_system_flag (const gchar *name)
4474 {
4475         struct flag_names_t *flag;
4476
4477         g_return_val_if_fail (name != NULL, 0);
4478
4479         for (flag = flag_names; flag->name; flag++)
4480                 if (!g_ascii_strcasecmp (name, flag->name))
4481                         return flag->value;
4482
4483         return 0;
4484 }
4485
4486 /**
4487  * camel_system_flag_get:
4488  * @flags: bitwise system flags
4489  * @name: name of the flag to check for
4490  *
4491  * Find the state of the flag @name in @flags.
4492  *
4493  * Returns: %TRUE if the named flag is set or %FALSE otherwise
4494  **/
4495 gboolean
4496 camel_system_flag_get (CamelMessageFlags flags,
4497                        const gchar *name)
4498 {
4499         g_return_val_if_fail (name != NULL, FALSE);
4500
4501         return flags & camel_system_flag (name);
4502 }
4503
4504 /**
4505  * camel_message_info_new:
4506  * @summary: a #CamelFolderSummary object or %NULL
4507  *
4508  * Create a new #CamelMessageInfo.
4509  *
4510  * Returns: a new #CamelMessageInfo
4511  **/
4512 gpointer
4513 camel_message_info_new (CamelFolderSummary *summary)
4514 {
4515         CamelFolderSummaryClass *class;
4516         CamelMessageInfo *info;
4517
4518         if (summary) {
4519                 camel_folder_summary_lock (summary, CAMEL_FOLDER_SUMMARY_ALLOC_LOCK);
4520                 class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
4521                 info = g_slice_alloc0 (class->message_info_size);
4522                 camel_folder_summary_unlock (summary, CAMEL_FOLDER_SUMMARY_ALLOC_LOCK);
4523         } else {
4524                 info = g_slice_alloc0 (sizeof (CamelMessageInfoBase));
4525         }
4526
4527         info->refcount = 1;
4528         info->summary = summary;
4529
4530         /* We assume that mi is always dirty unless freshly read or just saved*/
4531         ((CamelMessageInfoBase *) info)->dirty = TRUE;
4532
4533         return info;
4534 }
4535
4536 /**
4537  * camel_message_info_ref:
4538  * @info: a #CamelMessageInfo
4539  *
4540  * Reference an info.
4541  **/
4542 gpointer
4543 camel_message_info_ref (gpointer o)
4544 {
4545         CamelMessageInfo *mi = o;
4546
4547         if (mi->summary) {
4548                 camel_folder_summary_lock (mi->summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
4549                 g_assert (mi->refcount >= 1);
4550                 mi->refcount++;
4551                 camel_folder_summary_unlock (mi->summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
4552         } else {
4553                 GLOBAL_INFO_LOCK (info);
4554                 g_assert (mi->refcount >= 1);
4555                 mi->refcount++;
4556                 GLOBAL_INFO_UNLOCK (info);
4557         }
4558
4559         return o;
4560 }
4561
4562 /**
4563  * camel_message_info_new_from_header:
4564  * @summary: a #CamelFolderSummary object or %NULL
4565  * @header: raw header
4566  *
4567  * Create a new #CamelMessageInfo pre-populated with info from
4568  * @header.
4569  *
4570  * Returns: a new #CamelMessageInfo
4571  **/
4572 CamelMessageInfo *
4573 camel_message_info_new_from_header (CamelFolderSummary *summary,
4574                                     struct _camel_header_raw *header)
4575 {
4576         if (summary != NULL)
4577                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->
4578                         message_info_new_from_header (summary, header);
4579         else
4580                 return message_info_new_from_header (NULL, header);
4581 }
4582
4583 /**
4584  * camel_message_info_free:
4585  * @info: a #CamelMessageInfo
4586  *
4587  * Unref's and potentially frees a #CamelMessageInfo and its contents.
4588  **/
4589 void
4590 camel_message_info_free (gpointer o)
4591 {
4592         CamelMessageInfo *mi = o;
4593
4594         g_return_if_fail (mi != NULL);
4595
4596         if (mi->summary) {
4597                 camel_folder_summary_lock (mi->summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
4598
4599                 if (mi->refcount >= 1)
4600                         mi->refcount--;
4601                 if (mi->refcount > 0) {
4602                         camel_folder_summary_unlock (mi->summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
4603                         return;
4604                 }
4605
4606                 camel_folder_summary_unlock (mi->summary, CAMEL_FOLDER_SUMMARY_REF_LOCK);
4607
4608                 /* FIXME: this is kinda busted, should really be handled by message info free */
4609                 if (mi->summary->priv->build_content
4610                     && ((CamelMessageInfoBase *) mi)->content) {
4611                         camel_folder_summary_content_info_free (mi->summary, ((CamelMessageInfoBase *) mi)->content);
4612                         ((CamelMessageInfoBase *) mi)->content = NULL;
4613                 }
4614
4615                 CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->message_info_free (mi->summary, mi);
4616         } else {
4617                 GLOBAL_INFO_LOCK (info);
4618                 mi->refcount--;
4619                 if (mi->refcount > 0) {
4620                         GLOBAL_INFO_UNLOCK (info);
4621                         return;
4622                 }
4623                 GLOBAL_INFO_UNLOCK (info);
4624
4625                 message_info_free (NULL, mi);
4626         }
4627 }
4628
4629 /**
4630  * camel_message_info_clone:
4631  * @info: a #CamelMessageInfo
4632  *
4633  * Duplicate a #CamelMessageInfo.
4634  *
4635  * Returns: the duplicated #CamelMessageInfo
4636  **/
4637 gpointer
4638 camel_message_info_clone (gconstpointer o)
4639 {
4640         const CamelMessageInfo *mi = o;
4641
4642         if (mi->summary)
4643                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->message_info_clone (mi->summary, mi);
4644         else
4645                 return message_info_clone (NULL, mi);
4646 }
4647
4648 /**
4649  * camel_message_info_ptr:
4650  * @mi: a #CamelMessageInfo
4651  * @id: info to get
4652  *
4653  * Generic accessor method for getting pointer data.
4654  *
4655  * Returns: the pointer data
4656  **/
4657 gconstpointer
4658 camel_message_info_ptr (const CamelMessageInfo *mi,
4659                         gint id)
4660 {
4661         if (mi->summary)
4662                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_ptr (mi, id);
4663         else
4664                 return info_ptr (mi, id);
4665 }
4666
4667 /**
4668  * camel_message_info_uint32:
4669  * @mi: a #CamelMessageInfo
4670  * @id: info to get
4671  *
4672  * Generic accessor method for getting 32bit gint data.
4673  *
4674  * Returns: the gint data
4675  **/
4676 guint32
4677 camel_message_info_uint32 (const CamelMessageInfo *mi,
4678                            gint id)
4679 {
4680         if (mi->summary)
4681                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_uint32 (mi, id);
4682         else
4683                 return info_uint32 (mi, id);
4684 }
4685
4686 /**
4687  * camel_message_info_time:
4688  * @mi: a #CamelMessageInfo
4689  * @id: info to get
4690  *
4691  * Generic accessor method for getting time_t data.
4692  *
4693  * Returns: the time_t data
4694  **/
4695 time_t
4696 camel_message_info_time (const CamelMessageInfo *mi,
4697                          gint id)
4698 {
4699         if (mi->summary)
4700                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_time (mi, id);
4701         else
4702                 return info_time (mi, id);
4703 }
4704
4705 /**
4706  * camel_message_info_user_flag:
4707  * @mi: a #CamelMessageInfo
4708  * @id: user flag to get
4709  *
4710  * Get the state of a user flag named @id.
4711  *
4712  * Returns: the state of the user flag
4713  **/
4714 gboolean
4715 camel_message_info_user_flag (const CamelMessageInfo *mi,
4716                               const gchar *id)
4717 {
4718         if (mi->summary)
4719                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_user_flag (mi, id);
4720         else
4721                 return info_user_flag (mi, id);
4722 }
4723
4724 /**
4725  * camel_message_info_user_tag:
4726  * @mi: a #CamelMessageInfo
4727  * @id: user tag to get
4728  *
4729  * Get the value of a user tag named @id.
4730  *
4731  * Returns: the value of the user tag
4732  **/
4733 const gchar *
4734 camel_message_info_user_tag (const CamelMessageInfo *mi,
4735                              const gchar *id)
4736 {
4737         if (mi->summary)
4738                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_user_tag (mi, id);
4739         else
4740                 return info_user_tag (mi, id);
4741 }
4742
4743 /**
4744  * camel_message_info_set_flags:
4745  * @mi: a #CamelMessageInfo
4746  * @flags: mask of flags to change
4747  * @set: state the flags should be changed to
4748  *
4749  * Change the state of the system flags on the #CamelMessageInfo
4750  *
4751  * Returns: %TRUE if any of the flags changed or %FALSE otherwise
4752  **/
4753 gboolean
4754 camel_message_info_set_flags (CamelMessageInfo *mi,
4755                               CamelMessageFlags flags,
4756                               guint32 set)
4757 {
4758         if (mi->summary)
4759                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_set_flags (mi, flags, set);
4760         else
4761                 return info_set_flags (mi, flags, set);
4762 }
4763
4764 /**
4765  * camel_message_info_set_user_flag:
4766  * @mi: a #CamelMessageInfo
4767  * @id: name of the user flag to set
4768  * @state: state to set the flag to
4769  *
4770  * Set the state of a user flag on a #CamelMessageInfo.
4771  *
4772  * Returns: %TRUE if the state changed or %FALSE otherwise
4773  **/
4774 gboolean
4775 camel_message_info_set_user_flag (CamelMessageInfo *mi,
4776                                   const gchar *id,
4777                                   gboolean state)
4778 {
4779         if (mi->summary)
4780                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_set_user_flag (mi, id, state);
4781         else
4782                 return info_set_user_flag (mi, id, state);
4783 }
4784
4785 /**
4786  * camel_message_info_set_user_tag:
4787  * @mi: a #CamelMessageInfo
4788  * @id: name of the user tag to set
4789  * @val: value to set
4790  *
4791  * Set the value of a user tag on a #CamelMessageInfo.
4792  *
4793  * Returns: %TRUE if the value changed or %FALSE otherwise
4794  **/
4795 gboolean
4796 camel_message_info_set_user_tag (CamelMessageInfo *mi,
4797                                  const gchar *id,
4798                                  const gchar *val)
4799 {
4800         if (mi->summary)
4801                 return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->info_set_user_tag (mi, id, val);
4802         else
4803                 return info_set_user_tag (mi, id, val);
4804 }
4805
4806 void
4807 camel_content_info_dump (CamelMessageContentInfo *ci,
4808                          gint depth)
4809 {
4810         gchar *p;
4811
4812         p = alloca (depth * 4 + 1);
4813         memset (p, ' ', depth * 4);
4814         p[depth * 4] = 0;
4815
4816         if (ci == NULL) {
4817                 printf ("%s<empty>\n", p);
4818                 return;
4819         }
4820
4821         if (ci->type)
4822                 printf ("%scontent-type: %s/%s\n", p, ci->type->type ? ci->type->type : "(null)",
4823                         ci->type->subtype ? ci->type->subtype : "(null)");
4824         else
4825                 printf ("%scontent-type: <unset>\n", p);
4826         printf ("%scontent-transfer-encoding: %s\n", p, ci->encoding ? ci->encoding : "(null)");
4827         printf ("%scontent-description: %s\n", p, ci->description ? ci->description : "(null)");
4828         printf ("%ssize: %lu\n", p, (gulong) ci->size);
4829         ci = ci->childs;
4830         while (ci) {
4831                 camel_content_info_dump (ci, depth + 1);
4832                 ci = ci->next;
4833         }
4834 }
4835
4836 void
4837 camel_message_info_dump (CamelMessageInfo *mi)
4838 {
4839         if (mi == NULL) {
4840                 printf ("No message?\n");
4841                 return;
4842         }
4843
4844         printf ("Subject: %s\n", camel_message_info_subject (mi));
4845         printf ("To: %s\n", camel_message_info_to (mi));
4846         printf ("Cc: %s\n", camel_message_info_cc (mi));
4847         printf ("mailing list: %s\n", camel_message_info_mlist (mi));
4848         printf ("From: %s\n", camel_message_info_from (mi));
4849         printf ("UID: %s\n", camel_message_info_uid (mi));
4850         printf ("Flags: %04x\n", camel_message_info_flags (mi));
4851         camel_content_info_dump (((CamelMessageInfoBase *) mi)->content, 0);
4852 }
4853
4854 static gboolean
4855 compare_strings (const gchar *str1,
4856                  const gchar *str2)
4857 {
4858         if (str1 && str2 && !g_ascii_strcasecmp (str1, str2))
4859                 return TRUE;
4860         else if (!str1 && !str2)
4861                 return TRUE;
4862         else
4863                 return FALSE;
4864 }
4865
4866 static gboolean
4867 match_content_type (CamelContentType *info_ctype,
4868                     CamelContentType *ctype)
4869 {
4870         const gchar *name1, *name2;
4871
4872         if (!compare_strings (info_ctype->type, ctype->type))
4873                 return FALSE;
4874         if (!compare_strings (info_ctype->subtype, ctype->subtype))
4875                 return FALSE;
4876
4877         name1 = camel_content_type_param (info_ctype, "name");
4878         name2 = camel_content_type_param (ctype, "name");
4879         if (!compare_strings (name1, name2))
4880                 return FALSE;
4881
4882         return TRUE;
4883 }
4884
4885 /**
4886  * camel_folder_summary_guess_content_info:
4887  * @mi: a #CamelMessageInfo
4888  * @ctype: a #CamelContentType
4889  *
4890  * FIXME Document me!
4891  *
4892  * Since: 2.30
4893  **/
4894 const CamelMessageContentInfo *
4895 camel_folder_summary_guess_content_info (CamelMessageInfo *mi,
4896                                          CamelContentType *ctype)
4897 {
4898         const CamelMessageContentInfo *ci = camel_message_info_content (mi);
4899
4900         while (ci) {
4901                 const CamelMessageContentInfo *child = ci;
4902
4903                 do {
4904                         if (child->type && match_content_type (child->type, ctype))
4905                                 return child;
4906
4907                         child = child->next;
4908                 } while (child != NULL);
4909
4910                 ci = ci->childs;
4911         }
4912
4913         return NULL;
4914 }
4915
4916 /**
4917  * camel_folder_summary_lock:
4918  * @summary: a #CamelFolderSummary
4919  * @lock: lock type to lock
4920  *
4921  * Locks @summary's @lock. Unlock it with camel_folder_summary_unlock().
4922  *
4923  * Since: 2.32
4924  **/
4925 void
4926 camel_folder_summary_lock (CamelFolderSummary *summary,
4927                            CamelFolderSummaryLock lock)
4928 {
4929         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
4930
4931         switch (lock) {
4932                 case CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK:
4933                         g_static_rec_mutex_lock (&summary->priv->summary_lock);
4934                         break;
4935                 case CAMEL_FOLDER_SUMMARY_IO_LOCK:
4936                         g_static_rec_mutex_lock (&summary->priv->io_lock);
4937                         break;
4938                 case CAMEL_FOLDER_SUMMARY_FILTER_LOCK:
4939                         g_static_rec_mutex_lock (&summary->priv->filter_lock);
4940                         break;
4941                 case CAMEL_FOLDER_SUMMARY_ALLOC_LOCK:
4942                         g_static_rec_mutex_lock (&summary->priv->alloc_lock);
4943                         break;
4944                 case CAMEL_FOLDER_SUMMARY_REF_LOCK:
4945                         g_static_rec_mutex_lock (&summary->priv->ref_lock);
4946                         break;
4947                 default:
4948                         g_return_if_reached ();
4949         }
4950 }
4951
4952 /**
4953  * camel_folder_summary_unlock:
4954  * @summary: a #CamelFolderSummary
4955  * @lock: lock type to unlock
4956  *
4957  * Unlocks @summary's @lock, previously locked with camel_folder_summary_lock().
4958  *
4959  * Since: 2.32
4960  **/
4961 void
4962 camel_folder_summary_unlock (CamelFolderSummary *summary,
4963                              CamelFolderSummaryLock lock)
4964 {
4965         g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
4966
4967         switch (lock) {
4968                 case CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK:
4969                         g_static_rec_mutex_unlock (&summary->priv->summary_lock);
4970                         break;
4971                 case CAMEL_FOLDER_SUMMARY_IO_LOCK:
4972                         g_static_rec_mutex_unlock (&summary->priv->io_lock);
4973                         break;
4974                 case CAMEL_FOLDER_SUMMARY_FILTER_LOCK:
4975                         g_static_rec_mutex_unlock (&summary->priv->filter_lock);
4976                         break;
4977                 case CAMEL_FOLDER_SUMMARY_ALLOC_LOCK:
4978                         g_static_rec_mutex_unlock (&summary->priv->alloc_lock);
4979                         break;
4980                 case CAMEL_FOLDER_SUMMARY_REF_LOCK:
4981                         g_static_rec_mutex_unlock (&summary->priv->ref_lock);
4982                         break;
4983                 default:
4984                         g_return_if_reached ();
4985         }
4986 }
4987
4988 gint
4989 bdata_extract_digit (/* const */ gchar **part)
4990 {
4991         if (!part || !*part || !**part)
4992                 return 0;
4993
4994         if (**part == ' ')
4995                 *part += 1;
4996
4997         if (!**part)
4998                 return 0;
4999
5000         return strtoul (*part, part, 10);
5001 }
5002
5003 /* expecting "digit-value", where digit is length of the value */
5004 gchar *
5005 bdata_extract_string (/* const */ gchar **part)
5006 {
5007         gint len, has_len;
5008         gchar *val;
5009
5010         len = bdata_extract_digit (part);
5011
5012         /* might be a '-' sign */
5013         if (part && *part && **part)
5014                 *part += 1;
5015
5016         if (len <= 0 || !part || !*part || !**part)
5017                 return g_strdup ("");
5018
5019         if (!**part)
5020                 return g_strdup ("");
5021
5022         has_len = strlen (*part);
5023         if (has_len < len)
5024                 len = has_len;
5025
5026         val = g_strndup (*part, len);
5027         *part += len;
5028
5029         return val;
5030 }