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