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  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Michael Zucchi <notzed@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <ctype.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 
31 #include <glib/gi18n-lib.h>
32 #include <glib/gstdio.h>
33 
34 #include "camel-db.h"
35 #include "camel-debug.h"
36 #include "camel-file-utils.h"
37 #include "camel-folder-summary.h"
38 #include "camel-folder.h"
39 #include "camel-iconv.h"
40 #include "camel-message-info.h"
41 #include "camel-message-info-base.h"
42 #include "camel-mime-filter-basic.h"
43 #include "camel-mime-filter-charset.h"
44 #include "camel-mime-filter-html.h"
45 #include "camel-mime-filter-index.h"
46 #include "camel-mime-filter.h"
47 #include "camel-mime-message.h"
48 #include "camel-multipart.h"
49 #include "camel-session.h"
50 #include "camel-stream-filter.h"
51 #include "camel-stream-mem.h"
52 #include "camel-stream-null.h"
53 #include "camel-string-utils.h"
54 #include "camel-store.h"
55 #include "camel-utils.h"
56 #include "camel-vee-folder.h"
57 #include "camel-vtrash-folder.h"
58 #include "camel-mime-part-utils.h"
59 
60 /* Make 5 minutes as default cache drop */
61 #define SUMMARY_CACHE_DROP 300
62 #define dd(x) if (camel_debug("sync")) x
63 
64 struct _CamelFolderSummaryPrivate {
65 	/* header info */
66 	guint32 version;	/* version of file loaded/loading */
67 	gint64 timestamp;	/* timestamp for this summary (for implementors to use) */
68 	CamelFolderSummaryFlags flags;
69 
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 	GRecMutex summary_lock;	/* for the summary hashtable/array */
84 	GRecMutex filter_lock;	/* for accessing any of the filtering/indexing stuff, since we share them */
85 
86 	guint32 nextuid;	/* next uid? */
87 	guint32 saved_count;	/* how many were saved/loaded */
88 	guint32 unread_count;	/* handy totals */
89 	guint32 deleted_count;
90 	guint32 junk_count;
91 	guint32 junk_not_deleted_count;
92 	guint32 visible_count;
93 
94 	GHashTable *uids; /* uids of all known message infos; the 'value' are used flags for the message info */
95 	GHashTable *loaded_infos; /* uid->CamelMessageInfo *, those currently in memory */
96 
97 	struct _CamelFolder *folder; /* parent folder, for events */
98 	time_t cache_load_time;
99 	guint timeout_handle;
100 };
101 
102 /* this should probably be conditional on it existing */
103 #define USE_BSEARCH
104 
105 #define d(x)
106 #define io(x)			/* io debug */
107 #define w(x)
108 
109 #define CAMEL_FOLDER_SUMMARY_VERSION (14)
110 
111 /* trivial lists, just because ... */
112 struct _node {
113 	struct _node *next;
114 };
115 
116 static void cfs_schedule_info_release_timer (CamelFolderSummary *summary);
117 
118 static void summary_traverse_content_with_parser (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimeParser *mp);
119 static void summary_traverse_content_with_part (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimePart *object);
120 
121 static CamelMessageInfo * message_info_new_from_headers (CamelFolderSummary *, const CamelNameValueArray *);
122 static CamelMessageInfo * message_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
123 static CamelMessageInfo * message_info_new_from_message (CamelFolderSummary *summary, CamelMimeMessage *msg);
124 
125 static gint save_message_infos_to_db (CamelFolderSummary *summary, GError **error);
126 static gint camel_read_mir_callback (gpointer  ref, gint ncol, gchar ** cols, gchar ** name);
127 
128 static gchar *next_uid_string (CamelFolderSummary *summary);
129 
130 static CamelMessageInfo * message_info_from_uid (CamelFolderSummary *summary, const gchar *uid);
131 
132 enum {
133 	PROP_0,
134 	PROP_FOLDER,
135 	PROP_SAVED_COUNT,
136 	PROP_UNREAD_COUNT,
137 	PROP_DELETED_COUNT,
138 	PROP_JUNK_COUNT,
139 	PROP_JUNK_NOT_DELETED_COUNT,
140 	PROP_VISIBLE_COUNT
141 };
142 
143 enum {
144 	PREPARE_FETCH_ALL,
145 	LAST_SIGNAL
146 };
147 
148 static guint signals[LAST_SIGNAL];
149 
150 G_DEFINE_TYPE_WITH_PRIVATE (CamelFolderSummary, camel_folder_summary, G_TYPE_OBJECT)
151 
152 /* Private function */
153 void _camel_message_info_unset_summary (CamelMessageInfo *mi);
154 
155 static gboolean
remove_each_item(gpointer uid,gpointer mi,gpointer user_data)156 remove_each_item (gpointer uid,
157                   gpointer mi,
158                   gpointer user_data)
159 {
160 	GSList **to_remove_infos = user_data;
161 
162 	*to_remove_infos = g_slist_prepend (*to_remove_infos, mi);
163 
164 	return TRUE;
165 }
166 
167 static void
remove_all_loaded(CamelFolderSummary * summary)168 remove_all_loaded (CamelFolderSummary *summary)
169 {
170 	GSList *to_remove_infos = NULL, *link;
171 
172 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
173 
174 	camel_folder_summary_lock (summary);
175 
176 	g_hash_table_foreach_remove (summary->priv->loaded_infos, remove_each_item, &to_remove_infos);
177 
178 	for (link = to_remove_infos; link; link = g_slist_next (link)) {
179 		CamelMessageInfo *mi = link->data;
180 
181 		/* Dirty hack, to have CamelWeakRefGroup properly cleared,
182 		   when the message info leaks due to ref/unref imbalance. */
183 		_camel_message_info_unset_summary (mi);
184 	}
185 
186 	g_slist_free_full (to_remove_infos, g_object_unref);
187 
188 	camel_folder_summary_unlock (summary);
189 }
190 
191 static void
free_o_name(gpointer key,gpointer value,gpointer data)192 free_o_name (gpointer key,
193              gpointer value,
194              gpointer data)
195 {
196 	g_object_unref (value);
197 	g_free (key);
198 }
199 
200 static void
folder_summary_dispose(GObject * object)201 folder_summary_dispose (GObject *object)
202 {
203 	CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
204 
205 	if (summary->priv->timeout_handle) {
206 		/* this should not happen, because the release timer
207 		 * holds a reference on object */
208 		g_source_remove (summary->priv->timeout_handle);
209 		summary->priv->timeout_handle = 0;
210 	}
211 
212 	g_clear_object (&summary->priv->filter_index);
213 	g_clear_object (&summary->priv->filter_64);
214 	g_clear_object (&summary->priv->filter_qp);
215 	g_clear_object (&summary->priv->filter_uu);
216 	g_clear_object (&summary->priv->filter_save);
217 	g_clear_object (&summary->priv->filter_html);
218 	g_clear_object (&summary->priv->filter_stream);
219 	g_clear_object (&summary->priv->filter_index);
220 
221 	if (summary->priv->folder) {
222 		g_object_weak_unref (G_OBJECT (summary->priv->folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
223 		summary->priv->folder = NULL;
224 	}
225 
226 	/* Chain up to parent's dispose() method. */
227 	G_OBJECT_CLASS (camel_folder_summary_parent_class)->dispose (object);
228 }
229 
230 static void
folder_summary_finalize(GObject * object)231 folder_summary_finalize (GObject *object)
232 {
233 	CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
234 
235 	g_hash_table_destroy (summary->priv->uids);
236 	remove_all_loaded (summary);
237 	g_hash_table_destroy (summary->priv->loaded_infos);
238 
239 	g_hash_table_foreach (summary->priv->filter_charset, free_o_name, NULL);
240 	g_hash_table_destroy (summary->priv->filter_charset);
241 
242 	g_rec_mutex_clear (&summary->priv->summary_lock);
243 	g_rec_mutex_clear (&summary->priv->filter_lock);
244 
245 	/* Chain up to parent's finalize() method. */
246 	G_OBJECT_CLASS (camel_folder_summary_parent_class)->finalize (object);
247 }
248 
249 static void
folder_summary_set_folder(CamelFolderSummary * summary,CamelFolder * folder)250 folder_summary_set_folder (CamelFolderSummary *summary,
251                            CamelFolder *folder)
252 {
253 	g_return_if_fail (summary->priv->folder == NULL);
254 	/* folder can be NULL in certain cases, see maildir-store */
255 
256 	summary->priv->folder = folder;
257 	if (folder)
258 		g_object_weak_ref (G_OBJECT (folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
259 }
260 
261 static void
folder_summary_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)262 folder_summary_set_property (GObject *object,
263                              guint property_id,
264                              const GValue *value,
265                              GParamSpec *pspec)
266 {
267 	switch (property_id) {
268 		case PROP_FOLDER:
269 			folder_summary_set_folder (
270 				CAMEL_FOLDER_SUMMARY (object),
271 				CAMEL_FOLDER (g_value_get_object (value)));
272 			return;
273 	}
274 
275 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
276 }
277 
278 static void
folder_summary_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)279 folder_summary_get_property (GObject *object,
280                              guint property_id,
281                              GValue *value,
282                              GParamSpec *pspec)
283 {
284 	switch (property_id) {
285 		case PROP_FOLDER:
286 			g_value_set_object (
287 				value,
288 				camel_folder_summary_get_folder (
289 				CAMEL_FOLDER_SUMMARY (object)));
290 			return;
291 
292 		case PROP_SAVED_COUNT:
293 			g_value_set_uint (
294 				value,
295 				camel_folder_summary_get_saved_count (
296 				CAMEL_FOLDER_SUMMARY (object)));
297 			return;
298 
299 		case PROP_UNREAD_COUNT:
300 			g_value_set_uint (
301 				value,
302 				camel_folder_summary_get_unread_count (
303 				CAMEL_FOLDER_SUMMARY (object)));
304 			return;
305 
306 		case PROP_DELETED_COUNT:
307 			g_value_set_uint (
308 				value,
309 				camel_folder_summary_get_deleted_count (
310 				CAMEL_FOLDER_SUMMARY (object)));
311 			return;
312 
313 		case PROP_JUNK_COUNT:
314 			g_value_set_uint (
315 				value,
316 				camel_folder_summary_get_junk_count (
317 				CAMEL_FOLDER_SUMMARY (object)));
318 			return;
319 
320 		case PROP_JUNK_NOT_DELETED_COUNT:
321 			g_value_set_uint (
322 				value,
323 				camel_folder_summary_get_junk_not_deleted_count (
324 				CAMEL_FOLDER_SUMMARY (object)));
325 			return;
326 
327 		case PROP_VISIBLE_COUNT:
328 			g_value_set_uint (
329 				value,
330 				camel_folder_summary_get_visible_count (
331 				CAMEL_FOLDER_SUMMARY (object)));
332 			return;
333 	}
334 
335 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
336 }
337 
338 static gboolean
is_in_memory_summary(CamelFolderSummary * summary)339 is_in_memory_summary (CamelFolderSummary *summary)
340 {
341 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
342 
343 	return (summary->priv->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0;
344 }
345 
346 #define UPDATE_COUNTS_ADD		(1)
347 #define UPDATE_COUNTS_SUB		(2)
348 #define UPDATE_COUNTS_ADD_WITHOUT_TOTAL (3)
349 #define UPDATE_COUNTS_SUB_WITHOUT_TOTAL (4)
350 
351 static gboolean
folder_summary_update_counts_by_flags(CamelFolderSummary * summary,guint32 flags,gint op_type)352 folder_summary_update_counts_by_flags (CamelFolderSummary *summary,
353                                        guint32 flags,
354                                        gint op_type)
355 {
356 	gint unread = 0, deleted = 0, junk = 0;
357 	gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
358 	gboolean subtract = op_type == UPDATE_COUNTS_SUB || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
359 	gboolean without_total = op_type == UPDATE_COUNTS_ADD_WITHOUT_TOTAL || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
360 	gboolean changed = FALSE;
361 	GObject *summary_object;
362 
363 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
364 
365 	summary_object = G_OBJECT (summary);
366 
367 	if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
368 		CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
369 
370 		is_junk_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_JUNK;
371 		is_trash_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_TRASH;
372 	} else if (summary->priv->folder) {
373 		guint32 folder_flags;
374 
375 		folder_flags = camel_folder_get_flags (summary->priv->folder);
376 
377 		is_junk_folder = (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
378 		is_trash_folder = (folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
379 	}
380 
381 	if (!(flags & CAMEL_MESSAGE_SEEN))
382 		unread = subtract ? -1 : 1;
383 
384 	if (flags & CAMEL_MESSAGE_DELETED)
385 		deleted = subtract ? -1 : 1;
386 
387 	if (flags & CAMEL_MESSAGE_JUNK)
388 		junk = subtract ? -1 : 1;
389 
390 	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));
391 
392 	g_object_freeze_notify (summary_object);
393 
394 	if (deleted) {
395 		summary->priv->deleted_count += deleted;
396 		g_object_notify (summary_object, "deleted-count");
397 		changed = TRUE;
398 	}
399 
400 	if (junk) {
401 		summary->priv->junk_count += junk;
402 		g_object_notify (summary_object, "junk-count");
403 		changed = TRUE;
404 	}
405 
406 	if (junk && !deleted) {
407 		summary->priv->junk_not_deleted_count += junk;
408 		g_object_notify (summary_object, "junk-not-deleted-count");
409 		changed = TRUE;
410 	}
411 
412 	if (!junk && !deleted) {
413 		summary->priv->visible_count += subtract ? -1 : 1;
414 		g_object_notify (summary_object, "visible-count");
415 		changed = TRUE;
416 	}
417 
418 	if (junk && !is_junk_folder)
419 		unread = 0;
420 	if (deleted && !is_trash_folder)
421 		unread = 0;
422 
423 	if (unread) {
424 		if (unread > 0 || summary->priv->unread_count)
425 			summary->priv->unread_count += unread;
426 
427 		g_object_notify (summary_object, "unread-count");
428 		changed = TRUE;
429 	}
430 
431 	if (!without_total) {
432 		summary->priv->saved_count += subtract ? -1 : 1;
433 		g_object_notify (summary_object, "saved-count");
434 		changed = TRUE;
435 	}
436 
437 	if (changed)
438 		camel_folder_summary_touch (summary);
439 
440 	g_object_thaw_notify (summary_object);
441 
442 	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));
443 
444 	return changed;
445 }
446 
447 static gboolean
summary_header_load(CamelFolderSummary * summary,CamelFIRecord * record)448 summary_header_load (CamelFolderSummary *summary,
449 		     CamelFIRecord *record)
450 {
451 	io (printf ("Loading header from db \n"));
452 
453 	summary->priv->version = record->version;
454 
455 	/* We may not worry, as we are setting a new standard here */
456 #if 0
457 	/* Legacy version check, before version 12 we have no upgrade knowledge */
458 	if ((summary->priv->version > 0xff) && (summary->priv->version & 0xff) < 12) {
459 		io (printf ("Summary header version mismatch"));
460 		errno = EINVAL;
461 		return FALSE;
462 	}
463 
464 	if (!(summary->priv->version < 0x100 && summary->priv->version >= 13))
465 		io (printf ("Loading legacy summary\n"));
466 	else
467 		io (printf ("loading new-format summary\n"));
468 #endif
469 
470 	summary->priv->flags = record->flags;
471 	summary->priv->nextuid = record->nextuid;
472 	summary->priv->timestamp = record->timestamp;
473 	summary->priv->saved_count = record->saved_count;
474 
475 	summary->priv->unread_count = record->unread_count;
476 	summary->priv->deleted_count = record->deleted_count;
477 	summary->priv->junk_count = record->junk_count;
478 	summary->priv->visible_count = record->visible_count;
479 	summary->priv->junk_not_deleted_count = record->jnd_count;
480 
481 	return TRUE;
482 }
483 
484 static CamelFIRecord *
summary_header_save(CamelFolderSummary * summary,GError ** error)485 summary_header_save (CamelFolderSummary *summary,
486 		     GError **error)
487 {
488 	CamelFIRecord *record = g_new0 (CamelFIRecord, 1);
489 	CamelStore *parent_store;
490 	CamelDB *cdb;
491 	const gchar *table_name;
492 
493 	/* Though we are going to read, we do this during write,
494 	 * so lets use it that way. */
495 	table_name = camel_folder_get_full_name (summary->priv->folder);
496 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
497 	cdb = parent_store ? camel_store_get_db (parent_store) : NULL;
498 
499 	io (printf ("Savining header to db\n"));
500 
501 	record->folder_name = g_strdup (table_name);
502 
503 	/* we always write out the current version */
504 	record->version = CAMEL_FOLDER_SUMMARY_VERSION;
505 	record->flags = summary->priv->flags;
506 	record->nextuid = summary->priv->nextuid;
507 	record->timestamp = summary->priv->timestamp;
508 
509 	if (cdb && !is_in_memory_summary (summary)) {
510 		/* FIXME: Ever heard of Constructors and initializing ? */
511 		if (camel_db_count_total_message_info (cdb, table_name, &(record->saved_count), NULL))
512 			record->saved_count = 0;
513 		if (camel_db_count_junk_message_info (cdb, table_name, &(record->junk_count), NULL))
514 			record->junk_count = 0;
515 		if (camel_db_count_deleted_message_info (cdb, table_name, &(record->deleted_count), NULL))
516 			record->deleted_count = 0;
517 		if (camel_db_count_unread_message_info (cdb, table_name, &(record->unread_count), NULL))
518 			record->unread_count = 0;
519 		if (camel_db_count_visible_message_info (cdb, table_name, &(record->visible_count), NULL))
520 			record->visible_count = 0;
521 		if (camel_db_count_junk_not_deleted_message_info (cdb, table_name, &(record->jnd_count), NULL))
522 			record->jnd_count = 0;
523 	}
524 
525 	summary->priv->unread_count = record->unread_count;
526 	summary->priv->deleted_count = record->deleted_count;
527 	summary->priv->junk_count = record->junk_count;
528 	summary->priv->visible_count = record->visible_count;
529 	summary->priv->junk_not_deleted_count = record->jnd_count;
530 
531 	return record;
532 }
533 
534 /**
535  * camel_folder_summary_replace_flags:
536  * @summary: a #CamelFolderSummary
537  * @info: a #CamelMessageInfo
538  *
539  * Updates internal counts based on the flags in @info.
540  *
541  * Returns: Whether any count changed
542  *
543  * Since: 3.6
544  **/
545 gboolean
camel_folder_summary_replace_flags(CamelFolderSummary * summary,CamelMessageInfo * info)546 camel_folder_summary_replace_flags (CamelFolderSummary *summary,
547                                     CamelMessageInfo *info)
548 {
549 	guint32 old_flags, new_flags, added_flags, removed_flags;
550 	gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
551 	GObject *summary_object;
552 	gboolean changed = FALSE;
553 
554 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
555 	g_return_val_if_fail (info != NULL, FALSE);
556 
557 	if (!camel_message_info_get_uid (info) ||
558 	    !camel_folder_summary_check_uid (summary, camel_message_info_get_uid (info)))
559 		return FALSE;
560 
561 	summary_object = G_OBJECT (summary);
562 
563 	camel_folder_summary_lock (summary);
564 	g_object_freeze_notify (summary_object);
565 
566 	old_flags = GPOINTER_TO_UINT (g_hash_table_lookup (summary->priv->uids, camel_message_info_get_uid (info)));
567 	new_flags = camel_message_info_get_flags (info);
568 
569 	if ((old_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED) == (new_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED)) {
570 		g_object_thaw_notify (summary_object);
571 		camel_folder_summary_unlock (summary);
572 		return FALSE;
573 	}
574 
575 	if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
576 		CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
577 
578 		is_junk_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_JUNK;
579 		is_trash_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_TRASH;
580 	} else if (summary->priv->folder) {
581 		guint32 folder_flags;
582 
583 		folder_flags = camel_folder_get_flags (summary->priv->folder);
584 
585 		is_junk_folder = (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
586 		is_trash_folder = (folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
587 	}
588 
589 	added_flags = new_flags & (~(old_flags & new_flags));
590 	removed_flags = old_flags & (~(old_flags & new_flags));
591 
592 	if ((old_flags & CAMEL_MESSAGE_SEEN) == (new_flags & CAMEL_MESSAGE_SEEN)) {
593 		/* unread count is different from others, it asks for nonexistence
594 		 * of the flag, thus if it wasn't changed, then simply set it
595 		 * in added/removed, thus there are no false notifications
596 		 * on unread counts */
597 		added_flags |= CAMEL_MESSAGE_SEEN;
598 		removed_flags |= CAMEL_MESSAGE_SEEN;
599 	} else if ((!is_junk_folder && (new_flags & CAMEL_MESSAGE_JUNK) != 0 &&
600 		   (old_flags & CAMEL_MESSAGE_JUNK) == (new_flags & CAMEL_MESSAGE_JUNK)) ||
601 		   (!is_trash_folder && (new_flags & CAMEL_MESSAGE_DELETED) != 0 &&
602 		   (old_flags & CAMEL_MESSAGE_DELETED) == (new_flags & CAMEL_MESSAGE_DELETED))) {
603 		/* The message was set read or unread, but it is a junk or deleted message,
604 		 * in a non-Junk/non-Trash folder, thus it doesn't influence an unread count
605 		 * there, thus pretend unread didn't change */
606 		added_flags |= CAMEL_MESSAGE_SEEN;
607 		removed_flags |= CAMEL_MESSAGE_SEEN;
608 	}
609 
610 	/* decrement counts with removed flags */
611 	changed = folder_summary_update_counts_by_flags (summary, removed_flags, UPDATE_COUNTS_SUB_WITHOUT_TOTAL) || changed;
612 	/* increment counts with added flags */
613 	changed = folder_summary_update_counts_by_flags (summary, added_flags, UPDATE_COUNTS_ADD_WITHOUT_TOTAL) || changed;
614 
615 	/* update current flags on the summary */
616 	g_hash_table_insert (
617 		summary->priv->uids,
618 		(gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
619 		GUINT_TO_POINTER (new_flags));
620 
621 	g_object_thaw_notify (summary_object);
622 	camel_folder_summary_unlock (summary);
623 
624 	return changed;
625 }
626 
627 static void
camel_folder_summary_class_init(CamelFolderSummaryClass * class)628 camel_folder_summary_class_init (CamelFolderSummaryClass *class)
629 {
630 	GObjectClass *object_class;
631 
632 	object_class = G_OBJECT_CLASS (class);
633 	object_class->set_property = folder_summary_set_property;
634 	object_class->get_property = folder_summary_get_property;
635 	object_class->dispose = folder_summary_dispose;
636 	object_class->finalize = folder_summary_finalize;
637 
638 	class->message_info_type = CAMEL_TYPE_MESSAGE_INFO_BASE;
639 
640 	class->summary_header_load = summary_header_load;
641 	class->summary_header_save = summary_header_save;
642 
643 	class->message_info_new_from_headers = message_info_new_from_headers;
644 	class->message_info_new_from_parser = message_info_new_from_parser;
645 	class->message_info_new_from_message = message_info_new_from_message;
646 	class->message_info_from_uid = message_info_from_uid;
647 
648 	class->next_uid_string = next_uid_string;
649 
650 	/**
651 	 * CamelFolderSummary:folder
652 	 *
653 	 * The #CamelFolder to which the folder summary belongs.
654 	 **/
655 	g_object_class_install_property (
656 		object_class,
657 		PROP_FOLDER,
658 		g_param_spec_object (
659 			"folder",
660 			"Folder",
661 			"The folder to which the folder summary belongs",
662 			CAMEL_TYPE_FOLDER,
663 			G_PARAM_READWRITE |
664 			G_PARAM_CONSTRUCT_ONLY));
665 
666 	/**
667 	 * CamelFolderSummary:saved-count
668 	 *
669 	 * How many infos is saved in a summary.
670 	 **/
671 	g_object_class_install_property (
672 		object_class,
673 		PROP_SAVED_COUNT,
674 		g_param_spec_uint (
675 			"saved-count",
676 			"Saved count",
677 			"How many infos is savef in a summary",
678 			0,  G_MAXUINT32,
679 			0, G_PARAM_READABLE));
680 
681 	/**
682 	 * CamelFolderSummary:unread-count
683 	 *
684 	 * How many unread infos is saved in a summary.
685 	 **/
686 	g_object_class_install_property (
687 		object_class,
688 		PROP_UNREAD_COUNT,
689 		g_param_spec_uint (
690 			"unread-count",
691 			"Unread count",
692 			"How many unread infos is saved in a summary",
693 			0,  G_MAXUINT32,
694 			0, G_PARAM_READABLE));
695 
696 	/**
697 	 * CamelFolderSummary:deleted-count
698 	 *
699 	 * How many deleted infos is saved in a summary.
700 	 **/
701 	g_object_class_install_property (
702 		object_class,
703 		PROP_DELETED_COUNT,
704 		g_param_spec_uint (
705 			"deleted-count",
706 			"Deleted count",
707 			"How many deleted infos is saved in a summary",
708 			0,  G_MAXUINT32,
709 			0, G_PARAM_READABLE));
710 
711 	/**
712 	 * CamelFolderSummary:junk-count
713 	 *
714 	 * How many junk infos is saved in a summary.
715 	 **/
716 	g_object_class_install_property (
717 		object_class,
718 		PROP_JUNK_COUNT,
719 		g_param_spec_uint (
720 			"junk-count",
721 			"Junk count",
722 			"How many junk infos is saved in a summary",
723 			0,  G_MAXUINT32,
724 			0, G_PARAM_READABLE));
725 
726 	/**
727 	 * CamelFolderSummary:junk-not-deleted-count
728 	 *
729 	 * How many junk and not deleted infos is saved in a summary.
730 	 **/
731 	g_object_class_install_property (
732 		object_class,
733 		PROP_JUNK_NOT_DELETED_COUNT,
734 		g_param_spec_uint (
735 			"junk-not-deleted-count",
736 			"Junk not deleted count",
737 			"How many junk and not deleted infos is saved in a summary",
738 			0,  G_MAXUINT32,
739 			0, G_PARAM_READABLE));
740 
741 	/**
742 	 * CamelFolderSummary:visible-count
743 	 *
744 	 * How many visible (not deleted and not junk) infos is saved in a summary.
745 	 **/
746 	g_object_class_install_property (
747 		object_class,
748 		PROP_VISIBLE_COUNT,
749 		g_param_spec_uint (
750 			"visible-count",
751 			"Visible count",
752 			"How many visible (not deleted and not junk) infos is saved in a summary",
753 			0,  G_MAXUINT32,
754 			0, G_PARAM_READABLE));
755 
756 	/**
757 	 * CamelFolderSummary::prepare-fetch-all
758 	 * @summary: the #CamelFolderSummary which emitted the signal
759 	 *
760 	 * Emitted on call to camel_folder_summary_prepare_fetch_all().
761 	 *
762 	 * Since: 3.26
763 	 **/
764 	signals[PREPARE_FETCH_ALL] = g_signal_new (
765 		"changed",
766 		G_OBJECT_CLASS_TYPE (class),
767 		G_SIGNAL_RUN_FIRST,
768 		G_STRUCT_OFFSET (CamelFolderSummaryClass, prepare_fetch_all),
769 		NULL, NULL, NULL,
770 		G_TYPE_NONE, 0,
771 		G_TYPE_NONE);
772 }
773 
774 static void
camel_folder_summary_init(CamelFolderSummary * summary)775 camel_folder_summary_init (CamelFolderSummary *summary)
776 {
777 	summary->priv = camel_folder_summary_get_instance_private (summary);
778 
779 	summary->priv->version = CAMEL_FOLDER_SUMMARY_VERSION;
780 	summary->priv->flags = 0;
781 	summary->priv->timestamp = 0;
782 
783 	summary->priv->filter_charset = g_hash_table_new (
784 		camel_strcase_hash, camel_strcase_equal);
785 
786 	summary->priv->nextuid = 1;
787 	summary->priv->uids = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
788 	summary->priv->loaded_infos = g_hash_table_new (g_str_hash, g_str_equal);
789 
790 	g_rec_mutex_init (&summary->priv->summary_lock);
791 	g_rec_mutex_init (&summary->priv->filter_lock);
792 
793 	summary->priv->cache_load_time = 0;
794 	summary->priv->timeout_handle = 0;
795 }
796 
797 /**
798  * camel_folder_summary_new:
799  * @folder: (type CamelFolder): parent #CamelFolder object
800  *
801  * Create a new #CamelFolderSummary object.
802  *
803  * Returns: a new #CamelFolderSummary object
804  **/
805 CamelFolderSummary *
camel_folder_summary_new(CamelFolder * folder)806 camel_folder_summary_new (CamelFolder *folder)
807 {
808 	return g_object_new (CAMEL_TYPE_FOLDER_SUMMARY, "folder", folder, NULL);
809 }
810 
811 /**
812  * camel_folder_summary_get_folder:
813  * @summary: a #CamelFolderSummary object
814  *
815  * Returns: (transfer none): a #CamelFolder to which the summary if associated.
816  *
817  * Since: 3.4
818  **/
819 CamelFolder *
camel_folder_summary_get_folder(CamelFolderSummary * summary)820 camel_folder_summary_get_folder (CamelFolderSummary *summary)
821 {
822 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
823 
824 	return summary->priv->folder;
825 }
826 
827 /**
828  * camel_folder_summary_get_flags:
829  * @summary: a #CamelFolderSummary
830  *
831  * Returns: flags of the @summary, a bit-or of #CamelFolderSummaryFlags
832  *
833  * Since: 3.24
834  **/
835 guint32
camel_folder_summary_get_flags(CamelFolderSummary * summary)836 camel_folder_summary_get_flags (CamelFolderSummary *summary)
837 {
838 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
839 
840 	return summary->priv->flags;
841 }
842 
843 /**
844  * camel_folder_summary_set_flags:
845  * @summary: a #CamelFolderSummary
846  * @flags: flags to set
847  *
848  * Sets flags of the @summary, a bit-or of #CamelFolderSummaryFlags.
849  *
850  * Since: 3.24
851  **/
852 void
camel_folder_summary_set_flags(CamelFolderSummary * summary,guint32 flags)853 camel_folder_summary_set_flags (CamelFolderSummary *summary,
854 				guint32 flags)
855 {
856 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
857 
858 	summary->priv->flags = flags;
859 }
860 
861 /**
862  * camel_folder_summary_get_timestamp:
863  * @summary: a #CamelFolderSummary
864  *
865  * Returns: timestamp of the @summary, as set by the descendants
866  *
867  * Since: 3.24
868  **/
869 gint64
camel_folder_summary_get_timestamp(CamelFolderSummary * summary)870 camel_folder_summary_get_timestamp (CamelFolderSummary *summary)
871 {
872 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
873 
874 	return summary->priv->timestamp;
875 }
876 
877 /**
878  * camel_folder_summary_set_timestamp:
879  * @summary: a #CamelFolderSummary
880  * @timestamp: a timestamp to set
881  *
882  * Sets timestamp of the @summary, provided by the descendants. This doesn't
883  * change the 'dirty' flag of the @summary.
884  *
885  * Since: 3.24
886  **/
887 void
camel_folder_summary_set_timestamp(CamelFolderSummary * summary,gint64 timestamp)888 camel_folder_summary_set_timestamp (CamelFolderSummary *summary,
889 				    gint64 timestamp)
890 {
891 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
892 
893 	summary->priv->timestamp = timestamp;
894 }
895 
896 /**
897  * camel_folder_summary_get_version:
898  * @summary: a #CamelFolderSummary
899  *
900  * Returns: version of the @summary
901  *
902  * Since: 3.24
903  **/
904 guint32
camel_folder_summary_get_version(CamelFolderSummary * summary)905 camel_folder_summary_get_version (CamelFolderSummary *summary)
906 {
907 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
908 
909 	return summary->priv->version;
910 }
911 
912 /**
913  * camel_folder_summary_set_version:
914  * @summary: a #CamelFolderSummary
915  * @version: version to set
916  *
917  * Sets version of the @summary.
918  *
919  * Since: 3.24
920  **/
921 void
camel_folder_summary_set_version(CamelFolderSummary * summary,guint32 version)922 camel_folder_summary_set_version (CamelFolderSummary *summary,
923 				  guint32 version)
924 {
925 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
926 
927 	summary->priv->version = version;
928 }
929 
930 /**
931  * camel_folder_summary_get_saved_count:
932  * @summary: a #CamelFolderSummary object
933  *
934  * Returns: Count of saved infos.
935  *
936  * Since: 3.4
937  **/
938 guint32
camel_folder_summary_get_saved_count(CamelFolderSummary * summary)939 camel_folder_summary_get_saved_count (CamelFolderSummary *summary)
940 {
941 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
942 
943 	return summary->priv->saved_count;
944 }
945 
946 /**
947  * camel_folder_summary_get_unread_count:
948  * @summary: a #CamelFolderSummary object
949  *
950  * Returns: Count of unread infos.
951  *
952  * Since: 3.4
953  **/
954 guint32
camel_folder_summary_get_unread_count(CamelFolderSummary * summary)955 camel_folder_summary_get_unread_count (CamelFolderSummary *summary)
956 {
957 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
958 
959 	return summary->priv->unread_count;
960 }
961 
962 /**
963  * camel_folder_summary_get_deleted_count:
964  * @summary: a #CamelFolderSummary object
965  *
966  * Returns: Count of deleted infos.
967  *
968  * Since: 3.4
969  **/
970 guint32
camel_folder_summary_get_deleted_count(CamelFolderSummary * summary)971 camel_folder_summary_get_deleted_count (CamelFolderSummary *summary)
972 {
973 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
974 
975 	return summary->priv->deleted_count;
976 }
977 
978 /**
979  * camel_folder_summary_get_junk_count:
980  * @summary: a #CamelFolderSummary object
981  *
982  * Returns: Count of junk infos.
983  *
984  * Since: 3.4
985  **/
986 guint32
camel_folder_summary_get_junk_count(CamelFolderSummary * summary)987 camel_folder_summary_get_junk_count (CamelFolderSummary *summary)
988 {
989 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
990 
991 	return summary->priv->junk_count;
992 }
993 
994 /**
995  * camel_folder_summary_get_junk_not_deleted_count:
996  * @summary: a #CamelFolderSummary object
997  *
998  * Returns: Count of junk and not deleted infos.
999  *
1000  * Since: 3.4
1001  **/
1002 guint32
camel_folder_summary_get_junk_not_deleted_count(CamelFolderSummary * summary)1003 camel_folder_summary_get_junk_not_deleted_count (CamelFolderSummary *summary)
1004 {
1005 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1006 
1007 	return summary->priv->junk_not_deleted_count;
1008 }
1009 
1010 /**
1011  * camel_folder_summary_get_visible_count:
1012  * @summary: a #CamelFolderSummary object
1013  *
1014  * Returns: Count of visible (not junk and not deleted) infos.
1015  *
1016  * Since: 3.4
1017  **/
1018 guint32
camel_folder_summary_get_visible_count(CamelFolderSummary * summary)1019 camel_folder_summary_get_visible_count (CamelFolderSummary *summary)
1020 {
1021 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1022 
1023 	return summary->priv->visible_count;
1024 }
1025 
1026 /**
1027  * camel_folder_summary_set_index:
1028  * @summary: a #CamelFolderSummary object
1029  * @index: a #CamelIndex
1030  *
1031  * Set the index used to index body content.  If the index is %NULL, or
1032  * not set (the default), no indexing of body content will take place.
1033  **/
1034 void
camel_folder_summary_set_index(CamelFolderSummary * summary,CamelIndex * index)1035 camel_folder_summary_set_index (CamelFolderSummary *summary,
1036                                 CamelIndex *index)
1037 {
1038 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1039 
1040 	if (index != NULL)
1041 		g_object_ref (index);
1042 
1043 	if (summary->priv->index != NULL)
1044 		g_object_unref (summary->priv->index);
1045 
1046 	summary->priv->index = index;
1047 }
1048 
1049 /**
1050  * camel_folder_summary_get_index:
1051  * @summary: a #CamelFolderSummary object
1052  *
1053  * Returns: (transfer none): a #CamelIndex used to index body content.
1054  *
1055  * Since: 3.4
1056  **/
1057 CamelIndex *
camel_folder_summary_get_index(CamelFolderSummary * summary)1058 camel_folder_summary_get_index (CamelFolderSummary *summary)
1059 {
1060 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1061 
1062 	return summary->priv->index;
1063 }
1064 
1065 /**
1066  * camel_folder_summary_next_uid:
1067  * @summary: a #CamelFolderSummary object
1068  *
1069  * Generate a new unique uid value as an integer.  This
1070  * may be used to create a unique sequence of numbers.
1071  *
1072  * Returns: the next unique uid value
1073  **/
1074 guint32
camel_folder_summary_next_uid(CamelFolderSummary * summary)1075 camel_folder_summary_next_uid (CamelFolderSummary *summary)
1076 {
1077 	guint32 uid;
1078 
1079 	camel_folder_summary_lock (summary);
1080 
1081 	uid = summary->priv->nextuid++;
1082 	camel_folder_summary_touch (summary);
1083 
1084 	camel_folder_summary_unlock (summary);
1085 
1086 	return uid;
1087 }
1088 
1089 /**
1090  * camel_folder_summary_set_next_uid:
1091  * @summary: a #CamelFolderSummary object
1092  * @uid: The next minimum uid to assign.  To avoid clashing
1093  * uid's, set this to the uid of a given messages + 1.
1094  *
1095  * Set the next minimum uid available.  This can be used to
1096  * ensure new uid's do not clash with existing uid's.
1097  **/
1098 void
camel_folder_summary_set_next_uid(CamelFolderSummary * summary,guint32 uid)1099 camel_folder_summary_set_next_uid (CamelFolderSummary *summary,
1100                                    guint32 uid)
1101 {
1102 	camel_folder_summary_lock (summary);
1103 
1104 	summary->priv->nextuid = MAX (summary->priv->nextuid, uid);
1105 	camel_folder_summary_touch (summary);
1106 
1107 	camel_folder_summary_unlock (summary);
1108 }
1109 
1110 /**
1111  * camel_folder_summary_get_next_uid:
1112  * @summary: a #CamelFolderSummary object
1113  *
1114  * Returns: Next uid currently awaiting for assignment. The difference from
1115  *    camel_folder_summary_next_uid() is that this function returns actual
1116  *    value and doesn't increment it before returning.
1117  *
1118  * Since: 3.4
1119  **/
1120 guint32
camel_folder_summary_get_next_uid(CamelFolderSummary * summary)1121 camel_folder_summary_get_next_uid (CamelFolderSummary *summary)
1122 {
1123 	guint32 res;
1124 
1125 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1126 
1127 	camel_folder_summary_lock (summary);
1128 
1129 	res = summary->priv->nextuid;
1130 
1131 	camel_folder_summary_unlock (summary);
1132 
1133 	return res;
1134 }
1135 
1136 /**
1137  * camel_folder_summary_next_uid_string:
1138  * @summary: a #CamelFolderSummary object
1139  *
1140  * Retrieve the next uid, but as a formatted string.
1141  *
1142  * Returns: the next uid as an unsigned integer string.
1143  * This string must be freed by the caller.
1144  **/
1145 gchar *
camel_folder_summary_next_uid_string(CamelFolderSummary * summary)1146 camel_folder_summary_next_uid_string (CamelFolderSummary *summary)
1147 {
1148 	CamelFolderSummaryClass *class;
1149 
1150 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1151 
1152 	class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
1153 	g_return_val_if_fail (class != NULL, NULL);
1154 	g_return_val_if_fail (class->next_uid_string != NULL, NULL);
1155 
1156 	return class->next_uid_string (summary);
1157 }
1158 
1159 /**
1160  * camel_folder_summary_count:
1161  * @summary: a #CamelFolderSummary object
1162  *
1163  * Get the number of summary items stored in this summary.
1164  *
1165  * Returns: the number of items in the summary
1166  **/
1167 guint
camel_folder_summary_count(CamelFolderSummary * summary)1168 camel_folder_summary_count (CamelFolderSummary *summary)
1169 {
1170 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
1171 
1172 	return g_hash_table_size (summary->priv->uids);
1173 }
1174 
1175 /**
1176  * camel_folder_summary_check_uid
1177  * @summary: a #CamelFolderSummary object
1178  * @uid: a uid
1179  *
1180  * Check if the uid is valid. This isn't very efficient, so it shouldn't be called iteratively.
1181  *
1182  *
1183  * Returns: if the uid is present in the summary or not  (%TRUE or %FALSE)
1184  *
1185  * Since: 2.24
1186  **/
1187 gboolean
camel_folder_summary_check_uid(CamelFolderSummary * summary,const gchar * uid)1188 camel_folder_summary_check_uid (CamelFolderSummary *summary,
1189                                 const gchar *uid)
1190 {
1191 	gboolean ret;
1192 
1193 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1194 	g_return_val_if_fail (uid != NULL, FALSE);
1195 
1196 	camel_folder_summary_lock (summary);
1197 
1198 	ret = g_hash_table_contains (summary->priv->uids, uid);
1199 
1200 	camel_folder_summary_unlock (summary);
1201 
1202 	return ret;
1203 }
1204 
1205 static void
folder_summary_dupe_uids_to_array(gpointer key_uid,gpointer value_flags,gpointer user_data)1206 folder_summary_dupe_uids_to_array (gpointer key_uid,
1207                                    gpointer value_flags,
1208                                    gpointer user_data)
1209 {
1210 	g_ptr_array_add (user_data, (gpointer) camel_pstring_strdup (key_uid));
1211 }
1212 
1213 /**
1214  * camel_folder_summary_get_array:
1215  * @summary: a #CamelFolderSummary object
1216  *
1217  * Obtain a copy of the summary array.  This is done atomically,
1218  * so cannot contain empty entries.
1219  *
1220  * Free with camel_folder_summary_free_array()
1221  *
1222  * Returns: (element-type utf8) (transfer full): a #GPtrArray of uids
1223  *
1224  * Since: 3.4
1225  **/
1226 GPtrArray *
camel_folder_summary_get_array(CamelFolderSummary * summary)1227 camel_folder_summary_get_array (CamelFolderSummary *summary)
1228 {
1229 	GPtrArray *res;
1230 
1231 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1232 
1233 	camel_folder_summary_lock (summary);
1234 
1235 	/* Do not set free_func on the array, it would break IMAPx code */
1236 	res = g_ptr_array_sized_new (g_hash_table_size (summary->priv->uids));
1237 	g_hash_table_foreach (summary->priv->uids, folder_summary_dupe_uids_to_array, res);
1238 
1239 	camel_folder_summary_unlock (summary);
1240 
1241 	return res;
1242 }
1243 
1244 /**
1245  * camel_folder_summary_free_array:
1246  * @array: (element-type utf8): a #GPtrArray returned from camel_folder_summary_get_array()
1247  *
1248  * Free's array and its elements returned from camel_folder_summary_get_array().
1249  *
1250  * Since: 3.4
1251  **/
1252 void
camel_folder_summary_free_array(GPtrArray * array)1253 camel_folder_summary_free_array (GPtrArray *array)
1254 {
1255 	if (!array)
1256 		return;
1257 
1258 	g_ptr_array_foreach (array, (GFunc) camel_pstring_free, NULL);
1259 	g_ptr_array_free (array, TRUE);
1260 }
1261 
1262 static void
cfs_copy_uids_cb(gpointer key,gpointer value,gpointer user_data)1263 cfs_copy_uids_cb (gpointer key,
1264                   gpointer value,
1265                   gpointer user_data)
1266 {
1267 	const gchar *uid = key;
1268 	GHashTable *copy_hash = user_data;
1269 
1270 	g_hash_table_insert (copy_hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
1271 }
1272 
1273 /**
1274  * camel_folder_summary_get_hash:
1275  * @summary: a #CamelFolderSummary object
1276  *
1277  * Returns hash of current stored 'uids' in summary, where key is 'uid'
1278  * from the string pool, and value is 1. The returned pointer should
1279  * be freed with g_hash_table_destroy().
1280  *
1281  * Note: When searching for values always use uids from the string pool.
1282  *
1283  * Returns: (element-type utf8 gint) (transfer container):
1284  *
1285  * Since: 3.6
1286  **/
1287 GHashTable *
camel_folder_summary_get_hash(CamelFolderSummary * summary)1288 camel_folder_summary_get_hash (CamelFolderSummary *summary)
1289 {
1290 	GHashTable *uids;
1291 
1292 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1293 
1294 	camel_folder_summary_lock (summary);
1295 
1296 	/* using direct hash because of strings being from the string pool */
1297 	uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
1298 	g_hash_table_foreach (summary->priv->uids, cfs_copy_uids_cb, uids);
1299 
1300 	camel_folder_summary_unlock (summary);
1301 
1302 	return uids;
1303 }
1304 
1305 /**
1306  * camel_folder_summary_peek_loaded:
1307  * @summary: a #CamelFolderSummary
1308  * @uid: a message UID to look for
1309  *
1310  * Returns: (nullable) (transfer full): a #CamelMessageInfo for the given @uid,
1311  *    if it's currently loaded in memory, or %NULL otherwise. Unref the non-NULL
1312  *    info with g_object_unref() when done with it.
1313  *
1314  * Since: 2.26
1315  **/
1316 CamelMessageInfo *
camel_folder_summary_peek_loaded(CamelFolderSummary * summary,const gchar * uid)1317 camel_folder_summary_peek_loaded (CamelFolderSummary *summary,
1318                                   const gchar *uid)
1319 {
1320 	CamelMessageInfo *info;
1321 
1322 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1323 
1324 	camel_folder_summary_lock (summary);
1325 
1326 	info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1327 
1328 	if (info)
1329 		g_object_ref (info);
1330 
1331 	camel_folder_summary_unlock (summary);
1332 
1333 	return info;
1334 }
1335 
1336 struct _db_pass_data {
1337 	GHashTable *columns_hash;
1338 	CamelFolderSummary *summary;
1339 	gboolean add; /* or just insert to hashtable */
1340 };
1341 
1342 static CamelMessageInfo *
message_info_from_uid(CamelFolderSummary * summary,const gchar * uid)1343 message_info_from_uid (CamelFolderSummary *summary,
1344                        const gchar *uid)
1345 {
1346 	CamelMessageInfo *info;
1347 	gint ret;
1348 
1349 	camel_folder_summary_lock (summary);
1350 
1351 	info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1352 
1353 	if (!info) {
1354 		CamelDB *cdb;
1355 		CamelStore *parent_store;
1356 		const gchar *folder_name;
1357 		struct _db_pass_data data;
1358 
1359 		folder_name = camel_folder_get_full_name (summary->priv->folder);
1360 
1361 		if (is_in_memory_summary (summary)) {
1362 			camel_folder_summary_unlock (summary);
1363 			g_warning (
1364 				"%s: Tried to load uid '%s' "
1365 				"from DB on in-memory summary of '%s'",
1366 				G_STRFUNC, uid, folder_name);
1367 			return NULL;
1368 		}
1369 
1370 		parent_store = camel_folder_get_parent_store (summary->priv->folder);
1371 		if (!parent_store) {
1372 			camel_folder_summary_unlock (summary);
1373 			return NULL;
1374 		}
1375 
1376 		cdb = camel_store_get_db (parent_store);
1377 
1378 		data.columns_hash = NULL;
1379 		data.summary = summary;
1380 		data.add = FALSE;
1381 
1382 		ret = camel_db_read_message_info_record_with_uid (
1383 			cdb, folder_name, uid, &data,
1384 			camel_read_mir_callback, NULL);
1385 		if (data.columns_hash)
1386 			g_hash_table_destroy (data.columns_hash);
1387 
1388 		if (ret != 0) {
1389 			camel_folder_summary_unlock (summary);
1390 			return NULL;
1391 		}
1392 
1393 		/* We would have double reffed at camel_read_mir_callback */
1394 		info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
1395 
1396 		cfs_schedule_info_release_timer (summary);
1397 	}
1398 
1399 	if (info)
1400 		g_object_ref (info);
1401 
1402 	camel_folder_summary_unlock (summary);
1403 
1404 	return info;
1405 }
1406 
1407 /**
1408  * camel_folder_summary_get: (virtual message_info_from_uid)
1409  * @summary: a #CamelFolderSummary object
1410  * @uid: a uid
1411  *
1412  * Retrieve a summary item by uid.
1413  *
1414  * A referenced to the summary item is returned, which may be
1415  * ref'd or free'd as appropriate.
1416  *
1417  * Returns: (nullable) (transfer full): the summary item, or %NULL if the uid @uid is not available
1418  *
1419  * See camel_folder_summary_get_info_flags().
1420  *
1421  * Since: 3.4
1422  **/
1423 CamelMessageInfo *
camel_folder_summary_get(CamelFolderSummary * summary,const gchar * uid)1424 camel_folder_summary_get (CamelFolderSummary *summary,
1425                           const gchar *uid)
1426 {
1427 	CamelFolderSummaryClass *class;
1428 
1429 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1430 	g_return_val_if_fail (uid != NULL, NULL);
1431 
1432 	class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
1433 	g_return_val_if_fail (class != NULL, NULL);
1434 	g_return_val_if_fail (class->message_info_from_uid != NULL, NULL);
1435 
1436 	return class->message_info_from_uid (summary, uid);
1437 }
1438 
1439 /**
1440  * camel_folder_summary_get_info_flags:
1441  * @summary: a #CamelFolderSummary object
1442  * @uid: a uid
1443  *
1444  * Retrieve CamelMessageInfo::flags for a message info with UID @uid.
1445  * This is much quicker than camel_folder_summary_get(), because it
1446  * doesn't require reading the message info from a disk.
1447  *
1448  * Returns: the flags currently stored for message info with UID @uid,
1449  *          or (~0) on error
1450  *
1451  * Since: 3.12
1452  **/
1453 guint32
camel_folder_summary_get_info_flags(CamelFolderSummary * summary,const gchar * uid)1454 camel_folder_summary_get_info_flags (CamelFolderSummary *summary,
1455 				     const gchar *uid)
1456 {
1457 	gpointer ptr_uid = NULL, ptr_flags = NULL;
1458 
1459 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), (~0));
1460 	g_return_val_if_fail (uid != NULL, (~0));
1461 
1462 	camel_folder_summary_lock (summary);
1463 	if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
1464 		camel_folder_summary_unlock (summary);
1465 		return (~0);
1466 	}
1467 
1468 	camel_folder_summary_unlock (summary);
1469 
1470 	return GPOINTER_TO_UINT (ptr_flags);
1471 }
1472 
1473 static void
gather_dirty_or_flagged_uids(gpointer key,gpointer value,gpointer user_data)1474 gather_dirty_or_flagged_uids (gpointer key,
1475 			      gpointer value,
1476 			      gpointer user_data)
1477 {
1478 	const gchar *uid = key;
1479 	CamelMessageInfo *info = value;
1480 	GHashTable *hash = user_data;
1481 
1482 	if (camel_message_info_get_dirty (info) || (camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
1483 		g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
1484 }
1485 
1486 static void
gather_changed_uids(gpointer key,gpointer value,gpointer user_data)1487 gather_changed_uids (gpointer key,
1488                      gpointer value,
1489                      gpointer user_data)
1490 {
1491 	const gchar *uid = key;
1492 	guint32 flags = GPOINTER_TO_UINT (value);
1493 	GHashTable *hash = user_data;
1494 
1495 	if ((flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
1496 		g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
1497 }
1498 
1499 /**
1500  * camel_folder_summary_get_changed:
1501  * @summary: a #CamelFolderSummary
1502  *
1503  * Returns an array of changed UID-s. A UID is considered changed
1504  * when its corresponding CamelMesageInfo is 'dirty' or when it has
1505  * set the #CAMEL_MESSAGE_FOLDER_FLAGGED flag.
1506  *
1507  * Returns: (element-type utf8) (transfer full): a #GPtrArray with changed UID-s.
1508  *    Free it with camel_folder_summary_free_array() when no longer needed.
1509  *
1510  * Since: 2.24
1511  **/
1512 GPtrArray *
camel_folder_summary_get_changed(CamelFolderSummary * summary)1513 camel_folder_summary_get_changed (CamelFolderSummary *summary)
1514 {
1515 	GPtrArray *res;
1516 	GHashTable *hash;
1517 
1518 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
1519 
1520 	hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
1521 
1522 	camel_folder_summary_lock (summary);
1523 
1524 	g_hash_table_foreach (summary->priv->loaded_infos, gather_dirty_or_flagged_uids, hash);
1525 	g_hash_table_foreach (summary->priv->uids, gather_changed_uids, hash);
1526 
1527 	res = g_ptr_array_sized_new (g_hash_table_size (hash));
1528 	g_hash_table_foreach (hash, folder_summary_dupe_uids_to_array, res);
1529 
1530 	camel_folder_summary_unlock (summary);
1531 
1532 	g_hash_table_destroy (hash);
1533 
1534 	return res;
1535 }
1536 
1537 static void
count_changed_uids(gchar * key,CamelMessageInfo * info,gint * count)1538 count_changed_uids (gchar *key,
1539                     CamelMessageInfo *info,
1540                     gint *count)
1541 {
1542 	if (camel_message_info_get_dirty (info))
1543 		(*count)++;
1544 }
1545 
1546 static gint
cfs_count_dirty(CamelFolderSummary * summary)1547 cfs_count_dirty (CamelFolderSummary *summary)
1548 {
1549 	gint count = 0;
1550 
1551 	camel_folder_summary_lock (summary);
1552 	g_hash_table_foreach (summary->priv->loaded_infos, (GHFunc) count_changed_uids, &count);
1553 	camel_folder_summary_unlock (summary);
1554 
1555 	return count;
1556 }
1557 
1558 static gboolean
remove_item(gchar * uid,CamelMessageInfo * info,GSList ** to_remove_infos)1559 remove_item (gchar *uid,
1560              CamelMessageInfo *info,
1561              GSList **to_remove_infos)
1562 {
1563 	if (G_OBJECT (info)->ref_count == 1 && !camel_message_info_get_dirty (info) && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
1564 		*to_remove_infos = g_slist_prepend (*to_remove_infos, info);
1565 		return TRUE;
1566 	}
1567 
1568 	return FALSE;
1569 }
1570 
1571 static void
remove_cache(CamelSession * session,GCancellable * cancellable,CamelFolderSummary * summary,GError ** error)1572 remove_cache (CamelSession *session,
1573               GCancellable *cancellable,
1574               CamelFolderSummary *summary,
1575               GError **error)
1576 {
1577 	GSList *to_remove_infos = NULL;
1578 
1579 	camel_db_release_cache_memory ();
1580 
1581 	if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP)
1582 		return;
1583 
1584 	camel_folder_summary_lock (summary);
1585 
1586 	g_hash_table_foreach_remove (summary->priv->loaded_infos, (GHRFunc) remove_item, &to_remove_infos);
1587 
1588 	g_slist_free_full (to_remove_infos, g_object_unref);
1589 
1590 	camel_folder_summary_unlock (summary);
1591 
1592 	summary->priv->cache_load_time = time (NULL);
1593 }
1594 
1595 static void
cfs_free_weakref(gpointer ptr)1596 cfs_free_weakref (gpointer ptr)
1597 {
1598 	GWeakRef *weakref = ptr;
1599 
1600 	if (weakref) {
1601 		g_weak_ref_set (weakref, NULL);
1602 		g_weak_ref_clear (weakref);
1603 		g_slice_free (GWeakRef, weakref);
1604 	}
1605 }
1606 
1607 static gboolean
cfs_try_release_memory(gpointer user_data)1608 cfs_try_release_memory (gpointer user_data)
1609 {
1610 	GWeakRef *weakref = user_data;
1611 	CamelFolderSummary *summary;
1612 	CamelStore *parent_store;
1613 	CamelSession *session;
1614 	gchar *description;
1615 
1616 	g_return_val_if_fail (weakref != NULL, FALSE);
1617 
1618 	summary = g_weak_ref_get (weakref);
1619 
1620 	if (!summary)
1621 		return FALSE;
1622 
1623 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1624 
1625 	/* If folder is freed or if the cache is nil then clean up */
1626 	if (!summary->priv->folder ||
1627 	    !g_hash_table_size (summary->priv->loaded_infos) ||
1628 	    is_in_memory_summary (summary)) {
1629 		summary->priv->cache_load_time = 0;
1630 		summary->priv->timeout_handle = 0;
1631 		g_object_unref (summary);
1632 
1633 		return FALSE;
1634 	}
1635 
1636 	if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP) {
1637 		g_object_unref (summary);
1638 		return TRUE;
1639 	}
1640 
1641 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
1642 	if (!parent_store) {
1643 		summary->priv->cache_load_time = 0;
1644 		summary->priv->timeout_handle = 0;
1645 		g_object_unref (summary);
1646 
1647 		return FALSE;
1648 	}
1649 
1650 	session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
1651 	if (!session) {
1652 		summary->priv->cache_load_time = 0;
1653 		summary->priv->timeout_handle = 0;
1654 		g_object_unref (summary);
1655 
1656 		return FALSE;
1657 	}
1658 
1659 	/* Translators: The first “%s” is replaced with an account name and the second “%s”
1660 	   is replaced with a full path name. The spaces around “:” are intentional, as
1661 	   the whole “%s : %s” is meant as an absolute identification of the folder. */
1662 	description = g_strdup_printf (_("Release unused memory for folder “%s : %s”"),
1663 		camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
1664 		camel_folder_get_full_name (summary->priv->folder));
1665 
1666 	camel_session_submit_job (
1667 		session, description,
1668 		(CamelSessionCallback) remove_cache,
1669 		/* Consumes the reference of the 'summary'. */
1670 		summary, g_object_unref);
1671 
1672 	g_object_unref (session);
1673 	g_free (description);
1674 
1675 	return TRUE;
1676 }
1677 
1678 static void
cfs_schedule_info_release_timer(CamelFolderSummary * summary)1679 cfs_schedule_info_release_timer (CamelFolderSummary *summary)
1680 {
1681 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1682 
1683 	if (is_in_memory_summary (summary))
1684 		return;
1685 
1686 	if (!summary->priv->timeout_handle) {
1687 		static gboolean know_can_do = FALSE, can_do = TRUE;
1688 
1689 		if (!know_can_do) {
1690 			can_do = !g_getenv ("CAMEL_FREE_INFOS");
1691 			know_can_do = TRUE;
1692 		}
1693 
1694 		/* FIXME[disk-summary] LRU please and not timeouts */
1695 		if (can_do) {
1696 			GWeakRef *weakref;
1697 
1698 			weakref = g_slice_new0 (GWeakRef);
1699 			g_weak_ref_init (weakref, summary);
1700 
1701 			summary->priv->timeout_handle = g_timeout_add_seconds_full (
1702 				G_PRIORITY_DEFAULT, SUMMARY_CACHE_DROP,
1703 				cfs_try_release_memory,
1704 				weakref, cfs_free_weakref);
1705 			g_source_set_name_by_id (
1706 				summary->priv->timeout_handle,
1707 				"[camel] cfs_try_release_memory");
1708 		}
1709 	}
1710 
1711 	/* update also cache load time to the actual, to not release something just loaded */
1712 	summary->priv->cache_load_time = time (NULL);
1713 }
1714 
1715 static gint
cfs_cache_size(CamelFolderSummary * summary)1716 cfs_cache_size (CamelFolderSummary *summary)
1717 {
1718 	/* FIXME[disk-summary] this is a timely hack. fix it well */
1719 	if (!CAMEL_IS_VEE_FOLDER (summary->priv->folder))
1720 		return g_hash_table_size (summary->priv->loaded_infos);
1721 	else
1722 		return g_hash_table_size (summary->priv->uids);
1723 }
1724 
1725 static void
cfs_reload_from_db(CamelFolderSummary * summary,GError ** error)1726 cfs_reload_from_db (CamelFolderSummary *summary,
1727                     GError **error)
1728 {
1729 	CamelDB *cdb;
1730 	CamelStore *parent_store;
1731 	const gchar *folder_name;
1732 	struct _db_pass_data data;
1733 
1734 	/* FIXME[disk-summary] baseclass this, and vfolders we may have to
1735 	 * load better. */
1736 	d (printf ("\ncamel_folder_summary_reload_from_db called \n"));
1737 
1738 	if (is_in_memory_summary (summary))
1739 		return;
1740 
1741 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
1742 	if (!parent_store)
1743 		return;
1744 
1745 	folder_name = camel_folder_get_full_name (summary->priv->folder);
1746 	cdb = camel_store_get_db (parent_store);
1747 
1748 	data.columns_hash = NULL;
1749 	data.summary = summary;
1750 	data.add = FALSE;
1751 
1752 	camel_db_read_message_info_records (
1753 		cdb, folder_name, (gpointer) &data,
1754 		camel_read_mir_callback, NULL);
1755 
1756 	if (data.columns_hash)
1757 		g_hash_table_destroy (data.columns_hash);
1758 
1759 	cfs_schedule_info_release_timer (summary);
1760 }
1761 
1762 /**
1763  * camel_folder_summary_prepare_fetch_all:
1764  * @summary: #CamelFolderSummary object
1765  * @error: return location for a #GError, or %NULL
1766  *
1767  * Loads all infos into memory, if they are not yet and ensures
1768  * they will not be freed in next couple minutes. Call this function
1769  * before any mass operation or when all message infos will be needed,
1770  * for better performance.
1771  *
1772  * Since: 2.32
1773  **/
1774 void
camel_folder_summary_prepare_fetch_all(CamelFolderSummary * summary,GError ** error)1775 camel_folder_summary_prepare_fetch_all (CamelFolderSummary *summary,
1776                                         GError **error)
1777 {
1778 	guint loaded, known;
1779 
1780 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
1781 
1782 	loaded = cfs_cache_size (summary);
1783 	known = camel_folder_summary_count (summary);
1784 
1785 	g_signal_emit (summary, signals[PREPARE_FETCH_ALL], 0);
1786 
1787 	if (known - loaded > 50) {
1788 		camel_folder_summary_lock (summary);
1789 		cfs_reload_from_db (summary, error);
1790 		camel_folder_summary_unlock (summary);
1791 	}
1792 
1793 	/* update also cache load time, even when not loaded anything */
1794 	summary->priv->cache_load_time = time (NULL);
1795 }
1796 
1797 /**
1798  * camel_folder_summary_load:
1799  * @summary: a #CamelFolderSummary
1800  * @error: return location for a #GError, or %NULL
1801  *
1802  * Loads the summary from the disk. It also saves any pending
1803  * changes first.
1804  *
1805  * Returns: whether succeeded
1806  *
1807  * Since: 3.24
1808  **/
1809 gboolean
camel_folder_summary_load(CamelFolderSummary * summary,GError ** error)1810 camel_folder_summary_load (CamelFolderSummary *summary,
1811 			   GError **error)
1812 {
1813 	CamelFolderSummaryClass *klass;
1814 	CamelDB *cdb;
1815 	CamelStore *parent_store;
1816 	const gchar *full_name;
1817 	gint ret = 0;
1818 
1819 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
1820 
1821 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
1822 	g_return_val_if_fail (klass != NULL, FALSE);
1823 
1824 	if (is_in_memory_summary (summary))
1825 		return TRUE;
1826 
1827 	camel_folder_summary_lock (summary);
1828 	camel_folder_summary_save (summary, NULL);
1829 
1830 	/* struct _db_pass_data data; */
1831 	d (printf ("\ncamel_folder_summary_load called \n"));
1832 
1833 	full_name = camel_folder_get_full_name (summary->priv->folder);
1834 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
1835 	if (!camel_folder_summary_header_load (summary, parent_store, full_name, error)) {
1836 		camel_folder_summary_unlock (summary);
1837 		return FALSE;
1838 	}
1839 
1840 	if (!parent_store) {
1841 		camel_folder_summary_unlock (summary);
1842 		return FALSE;
1843 	}
1844 
1845 	cdb = camel_store_get_db (parent_store);
1846 
1847 	ret = camel_db_prepare_message_info_table (cdb, full_name, error);
1848 
1849 	if (ret == 0)
1850 		ret = camel_db_get_folder_uids (cdb, full_name, klass->sort_by, klass->collate, summary->priv->uids, error);
1851 
1852 	camel_folder_summary_unlock (summary);
1853 
1854 	return ret == 0;
1855 }
1856 
1857 /* Beware, it only borrows pointers from 'cols' here */
1858 static void
mir_from_cols(CamelMIRecord * mir,CamelFolderSummary * summary,GHashTable ** columns_hash,gint ncol,gchar ** cols,gchar ** name)1859 mir_from_cols (CamelMIRecord *mir,
1860                CamelFolderSummary *summary,
1861                GHashTable **columns_hash,
1862                gint ncol,
1863                gchar **cols,
1864                gchar **name)
1865 {
1866 	gint i;
1867 
1868 	for (i = 0; i < ncol; ++i) {
1869 		if (!name[i] || !cols[i])
1870 			continue;
1871 
1872 		switch (camel_db_get_column_ident (columns_hash, i, ncol, name)) {
1873 			case CAMEL_DB_COLUMN_UID:
1874 				mir->uid = cols[i];
1875 				break;
1876 			case CAMEL_DB_COLUMN_FLAGS:
1877 				mir->flags = cols[i] ? g_ascii_strtoull (cols[i], NULL, 10) : 0;
1878 				break;
1879 			case CAMEL_DB_COLUMN_READ:
1880 				mir->read = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1881 				break;
1882 			case CAMEL_DB_COLUMN_DELETED:
1883 				mir->deleted = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1884 				break;
1885 			case CAMEL_DB_COLUMN_REPLIED:
1886 				mir->replied = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1887 				break;
1888 			case CAMEL_DB_COLUMN_IMPORTANT:
1889 				mir->important = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1890 				break;
1891 			case CAMEL_DB_COLUMN_JUNK:
1892 				mir->junk = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1893 				break;
1894 			case CAMEL_DB_COLUMN_ATTACHMENT:
1895 				mir->attachment = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
1896 				break;
1897 			case CAMEL_DB_COLUMN_SIZE:
1898 				mir->size = cols[i] ? g_ascii_strtoull (cols[i], NULL, 10) : 0;
1899 				break;
1900 			case CAMEL_DB_COLUMN_DSENT:
1901 				mir->dsent = cols[i] ? g_ascii_strtoll (cols[i], NULL, 10) : 0;
1902 				break;
1903 			case CAMEL_DB_COLUMN_DRECEIVED:
1904 				mir->dreceived = cols[i] ? g_ascii_strtoll (cols[i], NULL, 10) : 0;
1905 				break;
1906 			case CAMEL_DB_COLUMN_SUBJECT:
1907 				mir->subject = cols[i];
1908 				break;
1909 			case CAMEL_DB_COLUMN_MAIL_FROM:
1910 				mir->from = cols[i];
1911 				break;
1912 			case CAMEL_DB_COLUMN_MAIL_TO:
1913 				mir->to = cols[i];
1914 				break;
1915 			case CAMEL_DB_COLUMN_MAIL_CC:
1916 				mir->cc = cols[i];
1917 				break;
1918 			case CAMEL_DB_COLUMN_MLIST:
1919 				mir->mlist = cols[i];
1920 				break;
1921 			case CAMEL_DB_COLUMN_FOLLOWUP_FLAG:
1922 				mir->followup_flag = cols[i];
1923 				break;
1924 			case CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON:
1925 				mir->followup_completed_on = cols[i];
1926 				break;
1927 			case CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY:
1928 				mir->followup_due_by = cols[i];
1929 				break;
1930 			case CAMEL_DB_COLUMN_PART:
1931 				mir->part = cols[i];
1932 				break;
1933 			case CAMEL_DB_COLUMN_LABELS:
1934 				mir->labels = cols[i];
1935 				break;
1936 			case CAMEL_DB_COLUMN_USERTAGS:
1937 				mir->usertags = cols[i];
1938 				break;
1939 			case CAMEL_DB_COLUMN_CINFO:
1940 				mir->cinfo = cols[i];
1941 				break;
1942 			case CAMEL_DB_COLUMN_BDATA:
1943 				mir->bdata = cols[i];
1944 				break;
1945 			case CAMEL_DB_COLUMN_USERHEADERS:
1946 				mir->userheaders = cols[i];
1947 				break;
1948 			case CAMEL_DB_COLUMN_PREVIEW:
1949 				mir->preview = cols[i];
1950 				break;
1951 			default:
1952 				g_warn_if_reached ();
1953 				break;
1954 		}
1955 	}
1956 }
1957 
1958 static gint
camel_read_mir_callback(gpointer ref,gint ncol,gchar ** cols,gchar ** name)1959 camel_read_mir_callback (gpointer ref,
1960                          gint ncol,
1961                          gchar **cols,
1962                          gchar **name)
1963 {
1964 	struct _db_pass_data *data = (struct _db_pass_data *) ref;
1965 	CamelFolderSummary *summary = data->summary;
1966 	CamelMIRecord mir;
1967 	CamelMessageInfo *info;
1968 	gchar *bdata_ptr;
1969 	gint ret = 0;
1970 
1971 	memset (&mir, 0, sizeof (CamelMIRecord));
1972 
1973 	/* As mir_from_cols() only borrows data from cols, no need to free mir */
1974 	mir_from_cols (&mir, summary, &data->columns_hash, ncol, cols, name);
1975 
1976 	camel_folder_summary_lock (summary);
1977 	if (!mir.uid || g_hash_table_lookup (summary->priv->loaded_infos, mir.uid)) {
1978 		/* Unlock and better return */
1979 		camel_folder_summary_unlock (summary);
1980 		return ret;
1981 	}
1982 	camel_folder_summary_unlock (summary);
1983 
1984 	info = camel_message_info_new (summary);
1985 	bdata_ptr = mir.bdata;
1986 	if (camel_message_info_load (info, &mir, &bdata_ptr)) {
1987 		/* Just now we are reading from the DB, it can't be dirty. */
1988 		camel_message_info_set_dirty (info, FALSE);
1989 		if (data->add) {
1990 			camel_folder_summary_add (summary, info, TRUE);
1991 			g_clear_object (&info);
1992 		} else {
1993 			camel_folder_summary_lock (summary);
1994 			/* Summary always holds a ref for the loaded infos; this consumes it */
1995 			g_hash_table_insert (summary->priv->loaded_infos, (gchar *) camel_message_info_get_uid (info), info);
1996 			camel_folder_summary_unlock (summary);
1997 		}
1998 	} else {
1999 		g_clear_object (&info);
2000 		g_warning ("Loading messageinfo from db failed");
2001 		ret = -1;
2002 	}
2003 
2004 	return ret;
2005 }
2006 
2007 typedef struct _SaveData {
2008 	CamelFolderSummary *summary;
2009 	const gchar *full_name;
2010 	CamelDB *cdb;
2011 	GError **out_error;
2012 } SaveData;
2013 
2014 static void
save_to_db_cb(gpointer key,gpointer value,gpointer user_data)2015 save_to_db_cb (gpointer key,
2016                gpointer value,
2017                gpointer user_data)
2018 {
2019 	CamelMessageInfo *mi = value;
2020 	CamelMIRecord *mir;
2021 	GString *bdata_str;
2022 	SaveData *dt = user_data;
2023 
2024 	g_return_if_fail (dt != NULL);
2025 
2026 	if (!camel_message_info_get_dirty (mi))
2027 		return;
2028 
2029 	mir = g_new0 (CamelMIRecord, 1);
2030 	bdata_str = g_string_new (NULL);
2031 
2032 	if (!camel_message_info_save (mi, mir, bdata_str)) {
2033 		g_warning ("Failed to save message info: %s\n", camel_message_info_get_uid (mi));
2034 		g_string_free (bdata_str, TRUE);
2035 		camel_db_camel_mir_free (mir);
2036 		return;
2037 	}
2038 
2039 	g_warn_if_fail (mir->bdata == NULL);
2040 	mir->bdata = g_string_free (bdata_str, FALSE);
2041 	bdata_str = NULL;
2042 
2043 	if (camel_db_write_message_info_record (dt->cdb, dt->full_name, mir, dt->out_error) != 0) {
2044 		camel_db_camel_mir_free (mir);
2045 		return;
2046 	}
2047 
2048 	/* Reset the dirty flag which decides if the changes are synced to the DB or not.
2049 	The FOLDER_FLAGGED should be used to check if the changes are synced to the server.
2050 	So, dont unset the FOLDER_FLAGGED flag */
2051 	camel_message_info_set_dirty (mi, FALSE);
2052 
2053 	camel_db_camel_mir_free (mir);
2054 }
2055 
2056 static gint
save_message_infos_to_db(CamelFolderSummary * summary,GError ** error)2057 save_message_infos_to_db (CamelFolderSummary *summary,
2058                           GError **error)
2059 {
2060 	CamelStore *parent_store;
2061 	CamelDB *cdb;
2062 	const gchar *full_name;
2063 	SaveData dt;
2064 
2065 	if (is_in_memory_summary (summary))
2066 		return 0;
2067 
2068 	full_name = camel_folder_get_full_name (summary->priv->folder);
2069 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
2070 	if (!parent_store)
2071 		return 0;
2072 
2073 	cdb = camel_store_get_db (parent_store);
2074 
2075 	if (camel_db_prepare_message_info_table (cdb, full_name, error) != 0)
2076 		return -1;
2077 
2078 	camel_folder_summary_lock (summary);
2079 
2080 	dt.summary = summary;
2081 	dt.full_name = full_name;
2082 	dt.cdb = cdb;
2083 	dt.out_error = error;
2084 
2085 	/* Push MessageInfo-es */
2086 	camel_db_begin_transaction (cdb, NULL);
2087 	g_hash_table_foreach (summary->priv->loaded_infos, save_to_db_cb, &dt);
2088 	camel_db_end_transaction (cdb, NULL);
2089 
2090 	camel_folder_summary_unlock (summary);
2091 	cfs_schedule_info_release_timer (summary);
2092 
2093 	return 0;
2094 }
2095 
2096 /**
2097  * camel_folder_summary_save:
2098  * @summary: a #CamelFolderSummary
2099  * @error: return location for a #GError, or %NULL
2100  *
2101  * Saves the content of the @summary to disk. It does nothing,
2102  * when the summary is not changed or when it doesn't support
2103  * permanent save.
2104  *
2105  * Returns: whether succeeded
2106  *
2107  * Since: 3.24
2108  **/
2109 gboolean
camel_folder_summary_save(CamelFolderSummary * summary,GError ** error)2110 camel_folder_summary_save (CamelFolderSummary *summary,
2111 			   GError **error)
2112 {
2113 	CamelFolderSummaryClass *klass;
2114 	CamelStore *parent_store;
2115 	CamelDB *cdb;
2116 	CamelFIRecord *record;
2117 	gint ret, count;
2118 
2119 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2120 
2121 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2122 	g_return_val_if_fail (klass != NULL, FALSE);
2123 	g_return_val_if_fail (klass->summary_header_save != NULL, FALSE);
2124 
2125 	if (!(summary->priv->flags & CAMEL_FOLDER_SUMMARY_DIRTY) ||
2126 	    is_in_memory_summary (summary))
2127 		return TRUE;
2128 
2129 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
2130 	if (!parent_store)
2131 		return FALSE;
2132 
2133 	cdb = camel_store_get_db (parent_store);
2134 
2135 	camel_folder_summary_lock (summary);
2136 
2137 	d (printf ("\ncamel_folder_summary_save called \n"));
2138 
2139 	summary->priv->flags &= ~CAMEL_FOLDER_SUMMARY_DIRTY;
2140 
2141 	count = cfs_count_dirty (summary);
2142 	if (!count) {
2143 		gboolean res = camel_folder_summary_header_save (summary, error);
2144 		camel_folder_summary_unlock (summary);
2145 		return res;
2146 	}
2147 
2148 	ret = save_message_infos_to_db (summary, error);
2149 	if (ret != 0) {
2150 		/* Failed, so lets reset the flag */
2151 		summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2152 		camel_folder_summary_unlock (summary);
2153 		return FALSE;
2154 	}
2155 
2156 	/* XXX So... if an error is set, how do we even reach this point
2157 	 *     given the above error check?  Oye vey this logic is nasty. */
2158 	if (error != NULL && *error != NULL &&
2159 		strstr ((*error)->message, "26 columns but 28 values") != NULL) {
2160 		const gchar *full_name;
2161 
2162 		full_name = camel_folder_get_full_name (summary->priv->folder);
2163 		g_warning ("Fixing up a broken summary migration on '%s : %s'\n",
2164 			camel_service_get_display_name (CAMEL_SERVICE (parent_store)), full_name);
2165 
2166 		/* Begin everything again. */
2167 		camel_db_begin_transaction (cdb, NULL);
2168 		camel_db_reset_folder_version (cdb, full_name, 0, NULL);
2169 		camel_db_end_transaction (cdb, NULL);
2170 
2171 		ret = save_message_infos_to_db (summary, error);
2172 		if (ret != 0) {
2173 			summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2174 			camel_folder_summary_unlock (summary);
2175 			return FALSE;
2176 		}
2177 	}
2178 
2179 	record = klass->summary_header_save (summary, error);
2180 	if (!record) {
2181 		summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2182 		camel_folder_summary_unlock (summary);
2183 		return FALSE;
2184 	}
2185 
2186 	camel_db_begin_transaction (cdb, NULL);
2187 	ret = camel_db_write_folder_info_record (cdb, record, error);
2188 	g_free (record->folder_name);
2189 	g_free (record->bdata);
2190 	g_free (record);
2191 
2192 	if (ret != 0) {
2193 		camel_db_abort_transaction (cdb, NULL);
2194 		summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2195 		camel_folder_summary_unlock (summary);
2196 		return FALSE;
2197 	}
2198 
2199 	camel_db_end_transaction (cdb, NULL);
2200 	camel_folder_summary_unlock (summary);
2201 
2202 	return ret == 0;
2203 }
2204 
2205 /**
2206  * camel_folder_summary_header_save:
2207  * @summary: a #CamelFolderSummary
2208  * @error: return location for a #GError, or %NULL
2209  *
2210  * Saves summary header information into the disk. The function does
2211  * nothing, if the summary doesn't support save to disk.
2212  *
2213  * Returns: whether succeeded
2214  *
2215  * Since: 3.24
2216  **/
2217 gboolean
camel_folder_summary_header_save(CamelFolderSummary * summary,GError ** error)2218 camel_folder_summary_header_save (CamelFolderSummary *summary,
2219 				  GError **error)
2220 {
2221 	CamelFolderSummaryClass *klass;
2222 	CamelStore *parent_store;
2223 	CamelFIRecord *record;
2224 	CamelDB *cdb;
2225 	gint ret;
2226 
2227 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2228 
2229 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2230 	g_return_val_if_fail (klass != NULL, FALSE);
2231 	g_return_val_if_fail (klass->summary_header_save != NULL, FALSE);
2232 
2233 	if (is_in_memory_summary (summary))
2234 		return TRUE;
2235 
2236 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
2237 	if (!parent_store)
2238 		return FALSE;
2239 
2240 	cdb = camel_store_get_db (parent_store);
2241 	camel_folder_summary_lock (summary);
2242 
2243 	d (printf ("\ncamel_folder_summary_header_save called \n"));
2244 
2245 	record = klass->summary_header_save (summary, error);
2246 	if (!record) {
2247 		camel_folder_summary_unlock (summary);
2248 		return FALSE;
2249 	}
2250 
2251 	camel_db_begin_transaction (cdb, NULL);
2252 	ret = camel_db_write_folder_info_record (cdb, record, error);
2253 	g_free (record->folder_name);
2254 	g_free (record->bdata);
2255 	g_free (record);
2256 
2257 	if (ret != 0) {
2258 		camel_db_abort_transaction (cdb, NULL);
2259 		camel_folder_summary_unlock (summary);
2260 		return FALSE;
2261 	}
2262 
2263 	camel_db_end_transaction (cdb, NULL);
2264 	camel_folder_summary_unlock (summary);
2265 
2266 	return ret == 0;
2267 }
2268 
2269 /**
2270  * camel_folder_summary_header_load:
2271  * @summary: a #CamelFolderSummary
2272  * @store: a #CamelStore
2273  * @folder_name: a folder name corresponding to @summary
2274  * @error: return location for a #GError, or %NULL
2275  *
2276  * Loads a summary header for the @summary, which corresponds to @folder_name
2277  * provided by @store.
2278  *
2279  * Returns: whether succeeded
2280  *
2281  * Since: 3.24
2282  **/
2283 gboolean
camel_folder_summary_header_load(CamelFolderSummary * summary,CamelStore * store,const gchar * folder_name,GError ** error)2284 camel_folder_summary_header_load (CamelFolderSummary *summary,
2285 				  CamelStore *store,
2286 				  const gchar *folder_name,
2287 				  GError **error)
2288 {
2289 	CamelFolderSummaryClass *klass;
2290 	CamelDB *cdb;
2291 	CamelFIRecord *record;
2292 	gboolean ret = FALSE;
2293 
2294 	d (printf ("\ncamel_folder_summary_header_load called \n"));
2295 
2296 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2297 
2298 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2299 	g_return_val_if_fail (klass != NULL, FALSE);
2300 	g_return_val_if_fail (klass->summary_header_load != NULL, FALSE);
2301 
2302 	if (is_in_memory_summary (summary))
2303 		return TRUE;
2304 
2305 	camel_folder_summary_lock (summary);
2306 	camel_folder_summary_save (summary, NULL);
2307 
2308 	cdb = camel_store_get_db (store);
2309 
2310 	record = g_new0 (CamelFIRecord, 1);
2311 	camel_db_read_folder_info_record (cdb, folder_name, record, error);
2312 
2313 	ret = klass->summary_header_load (summary, record);
2314 
2315 	camel_folder_summary_unlock (summary);
2316 
2317 	g_free (record->folder_name);
2318 	g_free (record->bdata);
2319 	g_free (record);
2320 
2321 	return ret;
2322 }
2323 
2324 static gboolean
summary_assign_uid(CamelFolderSummary * summary,CamelMessageInfo * info)2325 summary_assign_uid (CamelFolderSummary *summary,
2326                     CamelMessageInfo *info)
2327 {
2328 	const gchar *info_uid;
2329 	gchar *new_uid;
2330 	CamelMessageInfo *mi;
2331 
2332 	camel_message_info_set_abort_notifications (info, TRUE);
2333 	camel_message_info_property_lock (info);
2334 
2335 	info_uid = camel_message_info_get_uid (info);
2336 
2337 	if (!info_uid || !*info_uid) {
2338 		new_uid = camel_folder_summary_next_uid_string (summary);
2339 
2340 		camel_message_info_set_uid (info, new_uid);
2341 	} else {
2342 		new_uid = g_strdup (info_uid);
2343 	}
2344 
2345 	camel_folder_summary_lock (summary);
2346 
2347 	while ((mi = g_hash_table_lookup (summary->priv->loaded_infos, new_uid))) {
2348 		camel_folder_summary_unlock (summary);
2349 
2350 		g_free (new_uid);
2351 
2352 		if (mi == info) {
2353 			camel_message_info_property_unlock (info);
2354 			return FALSE;
2355 		}
2356 
2357 		d (printf ("Trying to insert message with clashing uid (%s).  new uid re-assigned", camel_message_info_get_uid (info)));
2358 
2359 		new_uid = camel_folder_summary_next_uid_string (summary);
2360 		camel_message_info_set_uid (info, new_uid);
2361 		camel_message_info_set_folder_flagged (info, TRUE);
2362 
2363 		camel_folder_summary_lock (summary);
2364 	}
2365 
2366 	g_free (new_uid);
2367 
2368 	camel_folder_summary_unlock (summary);
2369 
2370 	camel_message_info_property_unlock (info);
2371 	camel_message_info_set_abort_notifications (info, FALSE);
2372 
2373 	return TRUE;
2374 }
2375 
2376 /**
2377  * camel_folder_summary_add:
2378  * @summary: a #CamelFolderSummary object
2379  * @info: a #CamelMessageInfo
2380  * @force_keep_uid: whether to keep set UID of the @info
2381  *
2382  * Adds a new @info record to the summary. If the @force_keep_uid is %FALSE,
2383  * then a new uid is automatically re-assigned by calling
2384  * camel_folder_summary_next_uid_string(). It's an error to use
2385  * @force_keep_uid when the @info has none set.
2386  *
2387  * The @summary adds its own reference to @info, if needed, and any
2388  * previously loaded info is replaced with the new one.
2389  **/
2390 void
camel_folder_summary_add(CamelFolderSummary * summary,CamelMessageInfo * info,gboolean force_keep_uid)2391 camel_folder_summary_add (CamelFolderSummary *summary,
2392                           CamelMessageInfo *info,
2393 			  gboolean force_keep_uid)
2394 {
2395 	CamelMessageInfo *loaded_info;
2396 
2397 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
2398 
2399 	if (!info)
2400 		return;
2401 
2402 	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (info));
2403 
2404 	camel_folder_summary_lock (summary);
2405 	if (!force_keep_uid && !summary_assign_uid (summary, info)) {
2406 		camel_folder_summary_unlock (summary);
2407 		return;
2408 	}
2409 
2410 	if (force_keep_uid) {
2411 		const gchar *uid;
2412 
2413 		uid = camel_message_info_get_uid (info);
2414 		if (!uid || !*uid) {
2415 			g_warning ("%s: Cannot add message info without UID, when disabled to assign new UID; skipping it", G_STRFUNC);
2416 			camel_folder_summary_unlock (summary);
2417 			return;
2418 		}
2419 	}
2420 
2421 	folder_summary_update_counts_by_flags (summary, camel_message_info_get_flags (info), UPDATE_COUNTS_ADD);
2422 	camel_message_info_set_folder_flagged (info, TRUE);
2423 	camel_message_info_set_dirty (info, TRUE);
2424 
2425 	g_hash_table_insert (
2426 		summary->priv->uids,
2427 		(gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
2428 		GUINT_TO_POINTER (camel_message_info_get_flags (info)));
2429 
2430 	/* Summary always holds a ref for the loaded infos */
2431 	g_object_ref (info);
2432 
2433 	loaded_info = g_hash_table_lookup (summary->priv->loaded_infos, camel_message_info_get_uid (info));
2434 	if (loaded_info) {
2435 		/* Dirty hack, to have CamelWeakRefGroup properly cleared,
2436 		   when the message info leaks due to ref/unref imbalance. */
2437 		_camel_message_info_unset_summary (loaded_info);
2438 
2439 		g_clear_object (&loaded_info);
2440 	}
2441 
2442 	g_hash_table_insert (summary->priv->loaded_infos, (gpointer) camel_message_info_get_uid (info), info);
2443 
2444 	camel_folder_summary_touch (summary);
2445 
2446 	camel_folder_summary_unlock (summary);
2447 }
2448 
2449 /**
2450  * camel_folder_summary_info_new_from_headers: (virtual message_info_new_from_headers)
2451  * @summary: a #CamelFolderSummary object
2452  * @headers: rfc822 headers as #CamelNameValueArray
2453  *
2454  * Create a new info record from a header.
2455  *
2456  * Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
2457  *   with g_object_unref(), when done with it.
2458  *
2459  * Since: 3.24
2460  **/
2461 CamelMessageInfo *
camel_folder_summary_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)2462 camel_folder_summary_info_new_from_headers (CamelFolderSummary *summary,
2463 					    const CamelNameValueArray *headers)
2464 {
2465 	CamelFolderSummaryClass *class;
2466 
2467 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
2468 
2469 	class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2470 	g_return_val_if_fail (class != NULL, NULL);
2471 	g_return_val_if_fail (class->message_info_new_from_headers != NULL, NULL);
2472 
2473 	return class->message_info_new_from_headers (summary, headers);
2474 }
2475 
2476 /**
2477  * camel_folder_summary_info_new_from_parser: (virtual message_info_new_from_parser)
2478  * @summary: a #CamelFolderSummary object
2479  * @parser: a #CamelMimeParser object
2480  *
2481  * Create a new info record from a parser.  If the parser cannot
2482  * determine a uid, then none will be assigned.
2483  *
2484  * If indexing is enabled, and the parser cannot determine a new uid, then
2485  * one is automatically assigned.
2486  *
2487  * If indexing is enabled, then the content will be indexed based
2488  * on this new uid.  In this case, the message info MUST be
2489  * added using :add().
2490  *
2491  * Once complete, the parser will be positioned at the end of
2492  * the message.
2493  *
2494  * Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
2495  *   with g_object_unref(), when done with it.
2496  **/
2497 CamelMessageInfo *
camel_folder_summary_info_new_from_parser(CamelFolderSummary * summary,CamelMimeParser * mp)2498 camel_folder_summary_info_new_from_parser (CamelFolderSummary *summary,
2499                                            CamelMimeParser *mp)
2500 {
2501 	CamelFolderSummaryClass *klass;
2502 	CamelMessageInfo *info = NULL;
2503 	gchar *buffer;
2504 	gsize len;
2505 	goffset start;
2506 	CamelIndexName *name = NULL;
2507 
2508 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
2509 	g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), NULL);
2510 
2511 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2512 	g_return_val_if_fail (klass != NULL, NULL);
2513 	g_return_val_if_fail (klass->message_info_new_from_parser, NULL);
2514 
2515 	/* should this check the parser is in the right state, or assume it is?? */
2516 
2517 	start = camel_mime_parser_tell (mp);
2518 	if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_EOF) {
2519 		info = klass->message_info_new_from_parser (summary, mp);
2520 
2521 		camel_mime_parser_unstep (mp);
2522 
2523 		/* assign a unique uid, this is slightly 'wrong' as we do not really
2524 		 * know if we are going to store this in the summary, but no matter */
2525 		if (summary->priv->index)
2526 			summary_assign_uid (summary, info);
2527 
2528 		g_rec_mutex_lock (&summary->priv->filter_lock);
2529 
2530 		if (summary->priv->index) {
2531 			if (!summary->priv->filter_index)
2532 				summary->priv->filter_index = camel_mime_filter_index_new (summary->priv->index);
2533 			camel_index_delete_name (summary->priv->index, camel_message_info_get_uid (info));
2534 			name = camel_index_add_name (summary->priv->index, camel_message_info_get_uid (info));
2535 			camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), name);
2536 		}
2537 
2538 		/* always scan the content info, even if we dont save it */
2539 		summary_traverse_content_with_parser (summary, info, mp);
2540 
2541 		if (name && summary->priv->index) {
2542 			camel_index_write_name (summary->priv->index, name);
2543 			g_object_unref (name);
2544 			camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), NULL);
2545 		}
2546 
2547 		g_rec_mutex_unlock (&summary->priv->filter_lock);
2548 
2549 		camel_message_info_set_size (info, camel_mime_parser_tell (mp) - start);
2550 	}
2551 
2552 	return info;
2553 }
2554 
2555 /**
2556  * camel_folder_summary_info_new_from_message: (virtual message_info_new_from_message)
2557  * @summary: a #CamelFolderSummary object
2558  * @message: a #CamelMimeMessage object
2559  *
2560  * Create a summary item from a message.
2561  *
2562  * Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
2563  *   with g_object_unref(), when done with it.
2564  **/
2565 CamelMessageInfo *
camel_folder_summary_info_new_from_message(CamelFolderSummary * summary,CamelMimeMessage * msg)2566 camel_folder_summary_info_new_from_message (CamelFolderSummary *summary,
2567                                             CamelMimeMessage *msg)
2568 {
2569 	CamelFolderSummaryClass *klass;
2570 	CamelMessageInfo *info;
2571 	CamelIndexName *name = NULL;
2572 
2573 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
2574 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (msg), NULL);
2575 
2576 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2577 	g_return_val_if_fail (klass != NULL, NULL);
2578 	g_return_val_if_fail (klass->message_info_new_from_message != NULL, NULL);
2579 
2580 	info = klass->message_info_new_from_message (summary, msg);
2581 
2582 	/* assign a unique uid, this is slightly 'wrong' as we do not really
2583 	 * know if we are going to store this in the summary, but we need it set for indexing */
2584 	if (summary->priv->index)
2585 		summary_assign_uid (summary, info);
2586 
2587 	g_rec_mutex_lock (&summary->priv->filter_lock);
2588 
2589 	if (summary->priv->index) {
2590 		if (summary->priv->filter_index == NULL)
2591 			summary->priv->filter_index = camel_mime_filter_index_new (summary->priv->index);
2592 		camel_index_delete_name (summary->priv->index, camel_message_info_get_uid (info));
2593 		name = camel_index_add_name (summary->priv->index, camel_message_info_get_uid (info));
2594 		camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), name);
2595 
2596 		if (!summary->priv->filter_stream) {
2597 			CamelStream *null = camel_stream_null_new ();
2598 
2599 			summary->priv->filter_stream = camel_stream_filter_new (null);
2600 			g_object_unref (null);
2601 		}
2602 	}
2603 
2604 	summary_traverse_content_with_part (summary, info, (CamelMimePart *) msg);
2605 
2606 	if (name) {
2607 		camel_index_write_name (summary->priv->index, name);
2608 		g_object_unref (name);
2609 		camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), NULL);
2610 	}
2611 
2612 	g_rec_mutex_unlock (&summary->priv->filter_lock);
2613 
2614 	return info;
2615 }
2616 
2617 /**
2618  * camel_folder_summary_touch:
2619  * @summary: a #CamelFolderSummary object
2620  *
2621  * Mark the summary as changed, so that a save will force it to be
2622  * written back to disk.
2623  **/
2624 void
camel_folder_summary_touch(CamelFolderSummary * summary)2625 camel_folder_summary_touch (CamelFolderSummary *summary)
2626 {
2627 	camel_folder_summary_lock (summary);
2628 	summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
2629 	camel_folder_summary_unlock (summary);
2630 }
2631 
2632 /**
2633  * camel_folder_summary_clear:
2634  * @summary: a #CamelFolderSummary object
2635  * @error: return location for a #GError, or %NULL
2636  *
2637  * Empty the summary contents.
2638  *
2639  * Returns: whether succeeded
2640  **/
2641 gboolean
camel_folder_summary_clear(CamelFolderSummary * summary,GError ** error)2642 camel_folder_summary_clear (CamelFolderSummary *summary,
2643                             GError **error)
2644 {
2645 	GObject *summary_object;
2646 	CamelStore *parent_store;
2647 	CamelDB *cdb;
2648 	const gchar *folder_name;
2649 	gboolean res;
2650 
2651 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2652 
2653 	camel_folder_summary_lock (summary);
2654 	if (camel_folder_summary_count (summary) == 0) {
2655 		camel_folder_summary_unlock (summary);
2656 		return TRUE;
2657 	}
2658 
2659 	g_hash_table_remove_all (summary->priv->uids);
2660 	remove_all_loaded (summary);
2661 	g_hash_table_remove_all (summary->priv->loaded_infos);
2662 
2663 	summary->priv->saved_count = 0;
2664 	summary->priv->unread_count = 0;
2665 	summary->priv->deleted_count = 0;
2666 	summary->priv->junk_count = 0;
2667 	summary->priv->junk_not_deleted_count = 0;
2668 	summary->priv->visible_count = 0;
2669 
2670 	camel_folder_summary_touch (summary);
2671 
2672 	folder_name = camel_folder_get_full_name (summary->priv->folder);
2673 	parent_store = camel_folder_get_parent_store (summary->priv->folder);
2674 	if (!parent_store) {
2675 		camel_folder_summary_unlock (summary);
2676 		return FALSE;
2677 	}
2678 
2679 	cdb = camel_store_get_db (parent_store);
2680 
2681 	if (!is_in_memory_summary (summary))
2682 		res = camel_db_clear_folder_summary (cdb, folder_name, error) == 0;
2683 	else
2684 		res = TRUE;
2685 
2686 	summary_object = G_OBJECT (summary);
2687 	g_object_freeze_notify (summary_object);
2688 	g_object_notify (summary_object, "saved-count");
2689 	g_object_notify (summary_object, "unread-count");
2690 	g_object_notify (summary_object, "deleted-count");
2691 	g_object_notify (summary_object, "junk-count");
2692 	g_object_notify (summary_object, "junk-not-deleted-count");
2693 	g_object_notify (summary_object, "visible-count");
2694 	g_object_thaw_notify (summary_object);
2695 
2696 	camel_folder_summary_unlock (summary);
2697 
2698 	return res;
2699 }
2700 
2701 /**
2702  * camel_folder_summary_remove:
2703  * @summary: a #CamelFolderSummary object
2704  * @info: a #CamelMessageInfo
2705  *
2706  * Remove a specific @info record from the summary.
2707  *
2708  * Returns: Whether the @info was found and removed from the @summary.
2709  **/
2710 gboolean
camel_folder_summary_remove(CamelFolderSummary * summary,CamelMessageInfo * info)2711 camel_folder_summary_remove (CamelFolderSummary *summary,
2712                              CamelMessageInfo *info)
2713 {
2714 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2715 	g_return_val_if_fail (info != NULL, FALSE);
2716 
2717 	return camel_folder_summary_remove_uid (summary, camel_message_info_get_uid (info));
2718 }
2719 
2720 /**
2721  * camel_folder_summary_remove_uid:
2722  * @summary: a #CamelFolderSummary object
2723  * @uid: a uid
2724  *
2725  * Remove a specific info record from the summary, by @uid.
2726  *
2727  * Returns: Whether the @uid was found and removed from the @summary.
2728  **/
2729 gboolean
camel_folder_summary_remove_uid(CamelFolderSummary * summary,const gchar * uid)2730 camel_folder_summary_remove_uid (CamelFolderSummary *summary,
2731                                  const gchar *uid)
2732 {
2733 	gpointer ptr_uid = NULL, ptr_flags = NULL;
2734 	CamelMessageInfo *mi;
2735 	CamelStore *parent_store;
2736 	const gchar *full_name;
2737 	const gchar *uid_copy;
2738 	gboolean res = TRUE;
2739 
2740 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2741 	g_return_val_if_fail (uid != NULL, FALSE);
2742 
2743 	camel_folder_summary_lock (summary);
2744 	if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
2745 		camel_folder_summary_unlock (summary);
2746 		return FALSE;
2747 	}
2748 
2749 	folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
2750 
2751 	uid_copy = camel_pstring_strdup (uid);
2752 	g_hash_table_remove (summary->priv->uids, uid_copy);
2753 
2754 	mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
2755 	g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
2756 
2757 	if (mi) {
2758 		/* Dirty hack, to have CamelWeakRefGroup properly cleared,
2759 		   when the message info leaks due to ref/unref imbalance. */
2760 		_camel_message_info_unset_summary (mi);
2761 
2762 		g_clear_object (&mi);
2763 	}
2764 
2765 	if (!is_in_memory_summary (summary)) {
2766 		full_name = camel_folder_get_full_name (summary->priv->folder);
2767 		parent_store = camel_folder_get_parent_store (summary->priv->folder);
2768 		if (!parent_store || camel_db_delete_uid (camel_store_get_db (parent_store), full_name, uid_copy, NULL) != 0)
2769 			res = FALSE;
2770 	}
2771 
2772 	camel_pstring_free (uid_copy);
2773 
2774 	camel_folder_summary_touch (summary);
2775 	camel_folder_summary_unlock (summary);
2776 
2777 	return res;
2778 }
2779 
2780 /**
2781  * camel_folder_summary_remove_uids:
2782  * @summary: a #CamelFolderSummary object
2783  * @uids: (element-type utf8): a GList of uids
2784  *
2785  * Remove a specific info record from the summary, by @uid.
2786  *
2787  * Returns: Whether the @uid was found and removed from the @summary.
2788  *
2789  * Since: 3.6
2790  **/
2791 gboolean
camel_folder_summary_remove_uids(CamelFolderSummary * summary,GList * uids)2792 camel_folder_summary_remove_uids (CamelFolderSummary *summary,
2793                                   GList *uids)
2794 {
2795 	CamelStore *parent_store;
2796 	const gchar *full_name;
2797 	GList *l;
2798 	gboolean res = TRUE;
2799 
2800 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
2801 	g_return_val_if_fail (uids != NULL, FALSE);
2802 
2803 	g_object_freeze_notify (G_OBJECT (summary));
2804 	camel_folder_summary_lock (summary);
2805 
2806 	for (l = g_list_first (uids); l; l = g_list_next (l)) {
2807 		gpointer ptr_uid = NULL, ptr_flags = NULL;
2808 		if (g_hash_table_lookup_extended (summary->priv->uids, l->data, &ptr_uid, &ptr_flags)) {
2809 			const gchar *uid_copy = camel_pstring_strdup (l->data);
2810 			CamelMessageInfo *mi;
2811 
2812 			folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
2813 			g_hash_table_remove (summary->priv->uids, uid_copy);
2814 
2815 			mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
2816 			g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
2817 
2818 			if (mi) {
2819 				/* Dirty hack, to have CamelWeakRefGroup properly cleared,
2820 				   when the message info leaks due to ref/unref imbalance. */
2821 				_camel_message_info_unset_summary (mi);
2822 				g_clear_object (&mi);
2823 			}
2824 
2825 			camel_pstring_free (uid_copy);
2826 		}
2827 	}
2828 
2829 	if (!is_in_memory_summary (summary)) {
2830 		full_name = camel_folder_get_full_name (summary->priv->folder);
2831 		parent_store = camel_folder_get_parent_store (summary->priv->folder);
2832 		if (!parent_store || camel_db_delete_uids (camel_store_get_db (parent_store), full_name, uids, NULL) != 0)
2833 			res = FALSE;
2834 	}
2835 
2836 	camel_folder_summary_touch (summary);
2837 	camel_folder_summary_unlock (summary);
2838 	g_object_thaw_notify (G_OBJECT (summary));
2839 
2840 	return res;
2841 }
2842 
2843 /* are these even useful for anything??? */
2844 static CamelMessageInfo *
message_info_new_from_parser(CamelFolderSummary * summary,CamelMimeParser * mp)2845 message_info_new_from_parser (CamelFolderSummary *summary,
2846                               CamelMimeParser *mp)
2847 {
2848 	CamelFolderSummaryClass *klass;
2849 	CamelMessageInfo *mi = NULL;
2850 	CamelNameValueArray *headers;
2851 	gint state;
2852 
2853 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
2854 
2855 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2856 	g_return_val_if_fail (klass != NULL, NULL);
2857 	g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
2858 
2859 	state = camel_mime_parser_state (mp);
2860 	switch (state) {
2861 	case CAMEL_MIME_PARSER_STATE_HEADER:
2862 	case CAMEL_MIME_PARSER_STATE_MESSAGE:
2863 	case CAMEL_MIME_PARSER_STATE_MULTIPART:
2864 		headers = camel_mime_parser_dup_headers (mp);
2865 		mi = klass->message_info_new_from_headers (summary, headers);
2866 		camel_name_value_array_free (headers);
2867 		break;
2868 	default:
2869 		g_error ("Invalid parser state");
2870 	}
2871 
2872 	return mi;
2873 }
2874 
2875 static CamelMessageInfo *
message_info_new_from_message(CamelFolderSummary * summary,CamelMimeMessage * msg)2876 message_info_new_from_message (CamelFolderSummary *summary,
2877                                CamelMimeMessage *msg)
2878 {
2879 	CamelFolderSummaryClass *klass;
2880 
2881 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
2882 
2883 	klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
2884 	g_return_val_if_fail (klass != NULL, NULL);
2885 	g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
2886 
2887 	return klass->message_info_new_from_headers (summary, camel_medium_get_headers (CAMEL_MEDIUM (msg)));
2888 }
2889 
2890 static gchar *
summary_format_address(const CamelNameValueArray * headers,const gchar * name,const gchar * charset)2891 summary_format_address (const CamelNameValueArray *headers,
2892                         const gchar *name,
2893                         const gchar *charset)
2894 {
2895 	CamelHeaderAddress *addr = NULL;
2896 	gchar *text = NULL, *str = NULL;
2897 	const gchar *value;
2898 
2899 	value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, name);
2900 	if (!value)
2901 		return NULL;
2902 
2903 	while (*value && g_ascii_isspace (*value))
2904 		value++;
2905 
2906 	text = camel_header_unfold (value);
2907 
2908 	if ((addr = camel_header_address_decode (text, charset))) {
2909 		str = camel_header_address_list_format (addr);
2910 		camel_header_address_list_clear (&addr);
2911 		g_free (text);
2912 	} else {
2913 		str = text;
2914 	}
2915 
2916 	return str;
2917 }
2918 
2919 static gchar *
summary_format_string(const CamelNameValueArray * headers,const gchar * name,const gchar * charset)2920 summary_format_string (const CamelNameValueArray *headers,
2921                        const gchar *name,
2922                        const gchar *charset)
2923 {
2924 	gchar *text, *str;
2925 	const gchar *value;
2926 
2927 	value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, name);
2928 	if (!value)
2929 		return NULL;
2930 
2931 	while (*value && g_ascii_isspace (*value))
2932 		value++;
2933 
2934 	text = camel_header_unfold (value);
2935 	str = camel_header_decode_string (text, charset);
2936 	g_free (text);
2937 
2938 	return str;
2939 }
2940 
2941 static CamelMessageInfo *
message_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)2942 message_info_new_from_headers (CamelFolderSummary *summary,
2943 			       const CamelNameValueArray *headers)
2944 {
2945 	const gchar *received, *date, *content, *charset = NULL, *msgid;
2946 	GSList *refs, *irt, *scan;
2947 	gchar *subject, *from, *to, *cc, *mlist;
2948 	CamelContentType *ct = NULL;
2949 	CamelMessageInfo *mi;
2950 	guint count;
2951 
2952 	mi = camel_message_info_new (summary);
2953 
2954 	camel_message_info_set_abort_notifications (mi, TRUE);
2955 
2956 	if ((content = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Content-Type"))
2957 	     && (ct = camel_content_type_decode (content))
2958 	     && (charset = camel_content_type_param (ct, "charset"))
2959 	     && (g_ascii_strcasecmp (charset, "us-ascii") == 0))
2960 		charset = NULL;
2961 
2962 	charset = charset ? camel_iconv_charset_name (charset) : NULL;
2963 
2964 	subject = summary_format_string (headers, "subject", charset);
2965 	from = summary_format_address (headers, "from", charset);
2966 	to = summary_format_address (headers, "to", charset);
2967 	cc = summary_format_address (headers, "cc", charset);
2968 	mlist = camel_headers_dup_mailing_list (headers);
2969 
2970 	camel_message_info_set_subject (mi, subject);
2971 	camel_message_info_set_from (mi, from);
2972 	camel_message_info_set_to (mi, to);
2973 	camel_message_info_set_cc (mi, cc);
2974 	camel_message_info_set_mlist (mi, mlist);
2975 
2976 	g_free (subject);
2977 	g_free (from);
2978 	g_free (to);
2979 	g_free (cc);
2980 	g_free (mlist);
2981 
2982 	camel_util_fill_message_info_user_headers (mi, headers);
2983 
2984 	if ((date = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Date")))
2985 		camel_message_info_set_date_sent (mi, camel_header_decode_date (date, NULL));
2986 	else
2987 		camel_message_info_set_date_sent (mi, 0);
2988 
2989 	received = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Received");
2990 	if (received)
2991 		received = strrchr (received, ';');
2992 	if (received)
2993 		camel_message_info_set_date_received (mi, camel_header_decode_date (received + 1, NULL));
2994 	else
2995 		camel_message_info_set_date_received (mi, 0);
2996 
2997 	/* Fallback to Received date, when the Date header is missing */
2998 	if (!camel_message_info_get_date_sent (mi))
2999 		camel_message_info_set_date_sent (mi, camel_message_info_get_date_received (mi));
3000 
3001 	/* If neither Received is available, then use the current time. */
3002 	if (!camel_message_info_get_date_sent (mi))
3003 		camel_message_info_set_date_sent (mi, (gint64) time (NULL));
3004 
3005 	msgid = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Message-ID");
3006 	if (msgid)
3007 		camel_message_info_set_message_id (mi, camel_folder_search_util_hash_message_id (msgid, TRUE));
3008 
3009 	/* decode our references and in-reply-to headers */
3010 	refs = camel_header_references_decode (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "References"));
3011 	irt = camel_header_references_decode (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "In-Reply-To"));
3012 	if (refs || irt) {
3013 		GArray *references;
3014 
3015 		if (irt) {
3016 			/* The References field is populated from the "References" and/or "In-Reply-To"
3017 			 * headers. If both headers exist, take the first thing in the In-Reply-To header
3018 			 * that looks like a Message-ID, and append it to the References header. */
3019 
3020 			if (refs)
3021 				irt->next = refs;
3022 
3023 			refs = irt;
3024 		}
3025 
3026 		count = g_slist_length (refs);
3027 		references = g_array_sized_new (FALSE, FALSE, sizeof (guint64), count);
3028 
3029 		for (scan = refs; scan != NULL; scan = g_slist_next (scan)) {
3030 			guint64 msgid_hash;
3031 
3032 			msgid_hash = camel_folder_search_util_hash_message_id (scan->data, FALSE);
3033 
3034 			g_array_append_val (references, msgid_hash);
3035 		}
3036 		g_slist_free_full (refs, g_free);
3037 
3038 		camel_message_info_take_references (mi, references);
3039 	}
3040 
3041 	content = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Content-class");
3042 
3043 	if ((content && g_ascii_strcasecmp (content, "urn:content-classes:calendarmessage") == 0) ||
3044 	    (ct && camel_content_type_is (ct, "text", "calendar")) ||
3045 	    camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Calendar-Attachment"))
3046 		camel_message_info_set_user_flag (mi, "$has_cal", TRUE);
3047 
3048 	if (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Evolution-Note"))
3049 		camel_message_info_set_user_flag (mi, "$has_note", TRUE);
3050 
3051 	if (ct)
3052 		camel_content_type_unref (ct);
3053 
3054 	camel_message_info_take_headers (mi, camel_name_value_array_copy (headers));
3055 
3056 	camel_message_info_set_abort_notifications (mi, FALSE);
3057 
3058 	return mi;
3059 }
3060 
3061 static gchar *
next_uid_string(CamelFolderSummary * summary)3062 next_uid_string (CamelFolderSummary *summary)
3063 {
3064 	return g_strdup_printf ("%u", camel_folder_summary_next_uid (summary));
3065 }
3066 
3067 /*
3068   OK
3069   Now this is where all the "smarts" happen, where the content info is built,
3070   and any indexing and what not is performed
3071 */
3072 
3073 /* must have filter_lock before calling this function */
3074 static void
summary_traverse_content_with_parser(CamelFolderSummary * summary,CamelMessageInfo * msginfo,CamelMimeParser * mp)3075 summary_traverse_content_with_parser (CamelFolderSummary *summary,
3076 				      CamelMessageInfo *msginfo,
3077 				      CamelMimeParser *mp)
3078 {
3079 	gint state;
3080 	gsize len;
3081 	gchar *buffer;
3082 	CamelContentType *ct;
3083 	gint enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
3084 	CamelMimeFilter *mfc;
3085 	const gchar *calendar_header;
3086 
3087 	d (printf ("traversing content\n"));
3088 
3089 	/* start of this part */
3090 	state = camel_mime_parser_step (mp, &buffer, &len);
3091 
3092 	switch (state) {
3093 	case CAMEL_MIME_PARSER_STATE_HEADER:
3094 		/* check content type for indexing, then read body */
3095 		ct = camel_mime_parser_content_type (mp);
3096 		/* update attachments flag as we go */
3097 		if (camel_content_type_is (ct, "application", "pgp-signature")
3098 #ifdef ENABLE_SMIME
3099 		    || camel_content_type_is (ct, "application", "pkcs7-signature")
3100 		    || camel_content_type_is (ct, "application", "xpkcs7signature")
3101 		    || camel_content_type_is (ct, "application", "xpkcs7-signature")
3102 		    || camel_content_type_is (ct, "application", "x-pkcs7-signature")
3103 #endif
3104 			)
3105 			camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3106 
3107 		calendar_header = camel_mime_parser_header (mp, "Content-class", NULL);
3108 		if (calendar_header && g_ascii_strcasecmp (calendar_header, "urn:content-classes:calendarmessage") != 0)
3109 			calendar_header = NULL;
3110 
3111 		if (!calendar_header)
3112 			calendar_header = camel_mime_parser_header (mp, "X-Calendar-Attachment", NULL);
3113 
3114 		if (calendar_header || camel_content_type_is (ct, "text", "calendar"))
3115 			camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
3116 
3117 		if (camel_mime_parser_header (mp, "X-Evolution-Note", NULL))
3118 			camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
3119 
3120 		if (summary->priv->index && camel_content_type_is (ct, "text", "*")) {
3121 			gchar *encoding;
3122 			const gchar *charset;
3123 
3124 			d (printf ("generating index:\n"));
3125 
3126 			encoding = camel_content_transfer_encoding_decode (camel_mime_parser_header (mp, "content-transfer-encoding", NULL));
3127 			if (encoding) {
3128 				if (!g_ascii_strcasecmp (encoding, "base64")) {
3129 					d (printf (" decoding base64\n"));
3130 					if (summary->priv->filter_64 == NULL)
3131 						summary->priv->filter_64 = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
3132 					else
3133 						camel_mime_filter_reset (summary->priv->filter_64);
3134 					enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_64);
3135 				} else if (!g_ascii_strcasecmp (encoding, "quoted-printable")) {
3136 					d (printf (" decoding quoted-printable\n"));
3137 					if (summary->priv->filter_qp == NULL)
3138 						summary->priv->filter_qp = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_DEC);
3139 					else
3140 						camel_mime_filter_reset (summary->priv->filter_qp);
3141 					enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_qp);
3142 				} else if (!g_ascii_strcasecmp (encoding, "x-uuencode") ||
3143 					   !g_ascii_strcasecmp (encoding, "uuencode")) {
3144 					d (printf (" decoding x-uuencode\n"));
3145 					if (summary->priv->filter_uu == NULL)
3146 						summary->priv->filter_uu = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
3147 					else
3148 						camel_mime_filter_reset (summary->priv->filter_uu);
3149 					enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_uu);
3150 				} else {
3151 					d (printf (" ignoring encoding %s\n", encoding));
3152 				}
3153 				g_free (encoding);
3154 			}
3155 
3156 			charset = camel_content_type_param (ct, "charset");
3157 			if (charset != NULL
3158 			    && !(g_ascii_strcasecmp (charset, "us-ascii") == 0
3159 				 || g_ascii_strcasecmp (charset, "utf-8") == 0)) {
3160 				d (printf (" Adding conversion filter from %s to UTF-8\n", charset));
3161 				mfc = g_hash_table_lookup (summary->priv->filter_charset, charset);
3162 				if (mfc == NULL) {
3163 					mfc = camel_mime_filter_charset_new (charset, "UTF-8");
3164 					if (mfc)
3165 						g_hash_table_insert (summary->priv->filter_charset, g_strdup (charset), mfc);
3166 				} else {
3167 					camel_mime_filter_reset ((CamelMimeFilter *) mfc);
3168 				}
3169 				if (mfc) {
3170 					chr_id = camel_mime_parser_filter_add (mp, mfc);
3171 				} else {
3172 					w (g_warning ("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset));
3173 				}
3174 			}
3175 
3176 			/* we do charset conversions before this filter, which isn't strictly correct,
3177 			 * but works in most cases */
3178 			if (camel_content_type_is (ct, "text", "html")) {
3179 				if (summary->priv->filter_html == NULL)
3180 					summary->priv->filter_html = camel_mime_filter_html_new ();
3181 				else
3182 					camel_mime_filter_reset ((CamelMimeFilter *) summary->priv->filter_html);
3183 				html_id = camel_mime_parser_filter_add (mp, (CamelMimeFilter *) summary->priv->filter_html);
3184 			}
3185 
3186 			/* and this filter actually does the indexing */
3187 			idx_id = camel_mime_parser_filter_add (mp, summary->priv->filter_index);
3188 		}
3189 		/* and scan/index everything */
3190 		while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
3191 			;
3192 		/* and remove the filters */
3193 		camel_mime_parser_filter_remove (mp, enc_id);
3194 		camel_mime_parser_filter_remove (mp, chr_id);
3195 		camel_mime_parser_filter_remove (mp, html_id);
3196 		camel_mime_parser_filter_remove (mp, idx_id);
3197 		break;
3198 	case CAMEL_MIME_PARSER_STATE_MULTIPART:
3199 		d (printf ("Summarising multipart\n"));
3200 		/* update attachments flag as we go */
3201 		ct = camel_mime_parser_content_type (mp);
3202 		if (camel_content_type_is (ct, "multipart", "mixed"))
3203 			camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
3204 		if (camel_content_type_is (ct, "multipart", "signed")
3205 		    || camel_content_type_is (ct, "multipart", "encrypted"))
3206 			camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3207 
3208 		while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
3209 			camel_mime_parser_unstep (mp);
3210 			summary_traverse_content_with_parser (summary, msginfo, mp);
3211 		}
3212 		break;
3213 	case CAMEL_MIME_PARSER_STATE_MESSAGE:
3214 		d (printf ("Summarising message\n"));
3215 		/* update attachments flag as we go */
3216 		camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
3217 
3218 		summary_traverse_content_with_parser (summary, msginfo, mp);
3219 		state = camel_mime_parser_step (mp, &buffer, &len);
3220 		if (state != CAMEL_MIME_PARSER_STATE_MESSAGE_END) {
3221 			g_error ("Bad parser state: Expecing MESSAGE_END or MESSAGE_EOF, got: %d", state);
3222 			camel_mime_parser_unstep (mp);
3223 		}
3224 		break;
3225 	}
3226 
3227 	d (printf ("finished traversion content info\n"));
3228 }
3229 
3230 /* build the content-info, from a message */
3231 /* this needs the filter lock since it uses filters to perform indexing */
3232 static void
summary_traverse_content_with_part(CamelFolderSummary * summary,CamelMessageInfo * msginfo,CamelMimePart * object)3233 summary_traverse_content_with_part (CamelFolderSummary *summary,
3234                                     CamelMessageInfo *msginfo,
3235                                     CamelMimePart *object)
3236 {
3237 	CamelDataWrapper *containee;
3238 	gint parts, i;
3239 	CamelContentType *ct;
3240 	const CamelNameValueArray *headers;
3241 	gboolean is_calendar = FALSE, is_note = FALSE;
3242 	const gchar *header_name, *header_value;
3243 
3244 	containee = camel_medium_get_content (CAMEL_MEDIUM (object));
3245 
3246 	if (containee == NULL)
3247 		return;
3248 
3249 	/* TODO: I find it odd that get_part and get_content do not
3250 	 * add a reference, probably need fixing for multithreading */
3251 
3252 	/* check for attachments */
3253 	ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (containee));
3254 	if (camel_content_type_is (ct, "multipart", "*")) {
3255 		if (camel_content_type_is (ct, "multipart", "mixed"))
3256 			camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
3257 		if (camel_content_type_is (ct, "multipart", "signed")
3258 		    || camel_content_type_is (ct, "multipart", "encrypted"))
3259 			camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3260 	} else if (camel_content_type_is (ct, "application", "pgp-signature")
3261 #ifdef ENABLE_SMIME
3262 		    || camel_content_type_is (ct, "application", "pkcs7-signature")
3263 		    || camel_content_type_is (ct, "application", "xpkcs7signature")
3264 		    || camel_content_type_is (ct, "application", "xpkcs7-signature")
3265 		    || camel_content_type_is (ct, "application", "x-pkcs7-signature")
3266 #endif
3267 		) {
3268 		camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
3269 	}
3270 
3271 	headers = camel_medium_get_headers (CAMEL_MEDIUM (object));
3272 	for (i = 0; camel_name_value_array_get (headers, i, &header_name, &header_value); i++) {
3273 		const gchar *value = header_value;
3274 
3275 		while (value && *value && g_ascii_isspace (*value))
3276 			value++;
3277 
3278 		if (header_name && value && (
3279 		    (g_ascii_strcasecmp (header_name, "Content-class") == 0 && g_ascii_strcasecmp (value, "urn:content-classes:calendarmessage") == 0) ||
3280 		    (g_ascii_strcasecmp (header_name, "X-Calendar-Attachment") == 0))) {
3281 			is_calendar = TRUE;
3282 			if (is_note)
3283 				break;
3284 		}
3285 
3286 		if (header_name && value && g_ascii_strcasecmp (header_name, "X-Evolution-Note") == 0) {
3287 			is_note = TRUE;
3288 			if (is_calendar)
3289 				break;
3290 		}
3291 	}
3292 
3293 	if (is_calendar || camel_content_type_is (ct, "text", "calendar"))
3294 		camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
3295 
3296 	if (is_note)
3297 		camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
3298 
3299 	/* using the object types is more accurate than using the mime/types */
3300 	if (CAMEL_IS_MULTIPART (containee)) {
3301 		parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
3302 
3303 		for (i = 0; i < parts; i++) {
3304 			CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
3305 			g_return_if_fail (part);
3306 			summary_traverse_content_with_part (summary, msginfo, part);
3307 		}
3308 	} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
3309 		/* for messages we only look at its contents */
3310 		summary_traverse_content_with_part (summary, msginfo, (CamelMimePart *) containee);
3311 	} else if (summary->priv->filter_stream
3312 		   && camel_content_type_is (ct, "text", "*")) {
3313 		gint html_id = -1, idx_id = -1;
3314 
3315 		/* pre-attach html filter if required, otherwise just index filter */
3316 		if (camel_content_type_is (ct, "text", "html")) {
3317 			if (summary->priv->filter_html == NULL)
3318 				summary->priv->filter_html = camel_mime_filter_html_new ();
3319 			else
3320 				camel_mime_filter_reset ((CamelMimeFilter *) summary->priv->filter_html);
3321 			html_id = camel_stream_filter_add (
3322 				CAMEL_STREAM_FILTER (summary->priv->filter_stream),
3323 				(CamelMimeFilter *) summary->priv->filter_html);
3324 		}
3325 		idx_id = camel_stream_filter_add (
3326 			CAMEL_STREAM_FILTER (summary->priv->filter_stream),
3327 			summary->priv->filter_index);
3328 
3329 		/* FIXME Pass a GCancellable and GError here. */
3330 		camel_data_wrapper_decode_to_stream_sync (
3331 			containee, summary->priv->filter_stream, NULL, NULL);
3332 		camel_stream_flush (summary->priv->filter_stream, NULL, NULL);
3333 
3334 		camel_stream_filter_remove (
3335 			CAMEL_STREAM_FILTER (summary->priv->filter_stream), idx_id);
3336 		camel_stream_filter_remove (
3337 			CAMEL_STREAM_FILTER (summary->priv->filter_stream), html_id);
3338 	}
3339 }
3340 
3341 static struct flag_names_t {
3342 	const gchar *name;
3343 	guint32 value;
3344 } flag_names[] = {
3345 	{ "answered", CAMEL_MESSAGE_ANSWERED },
3346 	{ "deleted", CAMEL_MESSAGE_DELETED },
3347 	{ "draft", CAMEL_MESSAGE_DRAFT },
3348 	{ "flagged", CAMEL_MESSAGE_FLAGGED },
3349 	{ "seen", CAMEL_MESSAGE_SEEN },
3350 	{ "attachments", CAMEL_MESSAGE_ATTACHMENTS },
3351 	{ "junk", CAMEL_MESSAGE_JUNK },
3352 	{ "notjunk", CAMEL_MESSAGE_NOTJUNK },
3353 	{ "secure", CAMEL_MESSAGE_SECURE },
3354 	{ NULL, 0 }
3355 };
3356 
3357 /**
3358  * camel_system_flag:
3359  * @name: name of a system flag
3360  *
3361  * Returns: the integer value of the system flag string
3362  **/
3363 CamelMessageFlags
camel_system_flag(const gchar * name)3364 camel_system_flag (const gchar *name)
3365 {
3366 	struct flag_names_t *flag;
3367 
3368 	g_return_val_if_fail (name != NULL, 0);
3369 
3370 	for (flag = flag_names; flag->name; flag++)
3371 		if (!g_ascii_strcasecmp (name, flag->name))
3372 			return flag->value;
3373 
3374 	return 0;
3375 }
3376 
3377 /**
3378  * camel_system_flag_get:
3379  * @flags: bitwise system flags
3380  * @name: name of the flag to check for
3381  *
3382  * Find the state of the flag @name in @flags.
3383  *
3384  * Returns: %TRUE if the named flag is set or %FALSE otherwise
3385  **/
3386 gboolean
camel_system_flag_get(CamelMessageFlags flags,const gchar * name)3387 camel_system_flag_get (CamelMessageFlags flags,
3388                        const gchar *name)
3389 {
3390 	g_return_val_if_fail (name != NULL, FALSE);
3391 
3392 	return flags & camel_system_flag (name);
3393 }
3394 
3395 /**
3396  * camel_message_info_new_from_headers:
3397  * @summary: a #CamelFolderSummary object or %NULL
3398  * @headers: a #CamelNameValueArray
3399  *
3400  * Create a new #CamelMessageInfo pre-populated with info from
3401  * @headers.
3402  *
3403  * Returns: (transfer full): a new #CamelMessageInfo
3404  *
3405  * Since: 3.24
3406  **/
3407 CamelMessageInfo *
camel_message_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)3408 camel_message_info_new_from_headers (CamelFolderSummary *summary,
3409 				     const CamelNameValueArray *headers)
3410 {
3411 	if (summary != NULL) {
3412 		CamelFolderSummaryClass *klass;
3413 
3414 		g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
3415 
3416 		klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
3417 		g_return_val_if_fail (klass != NULL, NULL);
3418 		g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
3419 
3420 		return klass->message_info_new_from_headers (summary, headers);
3421 	} else {
3422 		return message_info_new_from_headers (NULL, headers);
3423 	}
3424 }
3425 
3426 /**
3427  * camel_folder_summary_lock:
3428  * @summary: a #CamelFolderSummary
3429  *
3430  * Locks @summary. Unlock it with camel_folder_summary_unlock().
3431  *
3432  * Since: 2.32
3433  **/
3434 void
camel_folder_summary_lock(CamelFolderSummary * summary)3435 camel_folder_summary_lock (CamelFolderSummary *summary)
3436 {
3437 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
3438 
3439 	g_rec_mutex_lock (&summary->priv->summary_lock);
3440 }
3441 
3442 /**
3443  * camel_folder_summary_unlock:
3444  * @summary: a #CamelFolderSummary
3445  *
3446  * Unlocks @summary, previously locked with camel_folder_summary_lock().
3447  *
3448  * Since: 2.32
3449  **/
3450 void
camel_folder_summary_unlock(CamelFolderSummary * summary)3451 camel_folder_summary_unlock (CamelFolderSummary *summary)
3452 {
3453 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
3454 
3455 	g_rec_mutex_unlock (&summary->priv->summary_lock);
3456 }
3457