/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
#include "evolution-data-server-config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "camel-db.h"
#include "camel-debug.h"
#include "camel-file-utils.h"
#include "camel-folder-summary.h"
#include "camel-folder.h"
#include "camel-iconv.h"
#include "camel-message-info.h"
#include "camel-message-info-base.h"
#include "camel-mime-filter-basic.h"
#include "camel-mime-filter-charset.h"
#include "camel-mime-filter-html.h"
#include "camel-mime-filter-index.h"
#include "camel-mime-filter.h"
#include "camel-mime-message.h"
#include "camel-multipart.h"
#include "camel-session.h"
#include "camel-stream-filter.h"
#include "camel-stream-mem.h"
#include "camel-stream-null.h"
#include "camel-string-utils.h"
#include "camel-store.h"
#include "camel-utils.h"
#include "camel-vee-folder.h"
#include "camel-vtrash-folder.h"
#include "camel-mime-part-utils.h"
/* Make 5 minutes as default cache drop */
#define SUMMARY_CACHE_DROP 300
#define dd(x) if (camel_debug("sync")) x
struct _CamelFolderSummaryPrivate {
/* header info */
guint32 version; /* version of file loaded/loading */
gint64 timestamp; /* timestamp for this summary (for implementors to use) */
CamelFolderSummaryFlags flags;
GHashTable *filter_charset; /* CamelMimeFilterCharset's indexed by source charset */
struct _CamelMimeFilter *filter_index;
struct _CamelMimeFilter *filter_64;
struct _CamelMimeFilter *filter_qp;
struct _CamelMimeFilter *filter_uu;
struct _CamelMimeFilter *filter_save;
struct _CamelMimeFilter *filter_html;
struct _CamelStream *filter_stream;
struct _CamelIndex *index;
GRecMutex summary_lock; /* for the summary hashtable/array */
GRecMutex filter_lock; /* for accessing any of the filtering/indexing stuff, since we share them */
guint32 nextuid; /* next uid? */
guint32 saved_count; /* how many were saved/loaded */
guint32 unread_count; /* handy totals */
guint32 deleted_count;
guint32 junk_count;
guint32 junk_not_deleted_count;
guint32 visible_count;
GHashTable *uids; /* uids of all known message infos; the 'value' are used flags for the message info */
GHashTable *loaded_infos; /* uid->CamelMessageInfo *, those currently in memory */
struct _CamelFolder *folder; /* parent folder, for events */
time_t cache_load_time;
guint timeout_handle;
};
/* this should probably be conditional on it existing */
#define USE_BSEARCH
#define d(x)
#define io(x) /* io debug */
#define w(x)
#define CAMEL_FOLDER_SUMMARY_VERSION (14)
/* trivial lists, just because ... */
struct _node {
struct _node *next;
};
static void cfs_schedule_info_release_timer (CamelFolderSummary *summary);
static void summary_traverse_content_with_parser (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimeParser *mp);
static void summary_traverse_content_with_part (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimePart *object);
static CamelMessageInfo * message_info_new_from_headers (CamelFolderSummary *, const CamelNameValueArray *);
static CamelMessageInfo * message_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
static CamelMessageInfo * message_info_new_from_message (CamelFolderSummary *summary, CamelMimeMessage *msg);
static gint save_message_infos_to_db (CamelFolderSummary *summary, GError **error);
static gint camel_read_mir_callback (gpointer ref, gint ncol, gchar ** cols, gchar ** name);
static gchar *next_uid_string (CamelFolderSummary *summary);
static CamelMessageInfo * message_info_from_uid (CamelFolderSummary *summary, const gchar *uid);
enum {
PROP_0,
PROP_FOLDER,
PROP_SAVED_COUNT,
PROP_UNREAD_COUNT,
PROP_DELETED_COUNT,
PROP_JUNK_COUNT,
PROP_JUNK_NOT_DELETED_COUNT,
PROP_VISIBLE_COUNT
};
enum {
PREPARE_FETCH_ALL,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE_WITH_PRIVATE (CamelFolderSummary, camel_folder_summary, G_TYPE_OBJECT)
/* Private function */
void _camel_message_info_unset_summary (CamelMessageInfo *mi);
static gboolean
remove_each_item (gpointer uid,
gpointer mi,
gpointer user_data)
{
GSList **to_remove_infos = user_data;
*to_remove_infos = g_slist_prepend (*to_remove_infos, mi);
return TRUE;
}
static void
remove_all_loaded (CamelFolderSummary *summary)
{
GSList *to_remove_infos = NULL, *link;
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
camel_folder_summary_lock (summary);
g_hash_table_foreach_remove (summary->priv->loaded_infos, remove_each_item, &to_remove_infos);
for (link = to_remove_infos; link; link = g_slist_next (link)) {
CamelMessageInfo *mi = link->data;
/* Dirty hack, to have CamelWeakRefGroup properly cleared,
when the message info leaks due to ref/unref imbalance. */
_camel_message_info_unset_summary (mi);
}
g_slist_free_full (to_remove_infos, g_object_unref);
camel_folder_summary_unlock (summary);
}
static void
free_o_name (gpointer key,
gpointer value,
gpointer data)
{
g_object_unref (value);
g_free (key);
}
static void
folder_summary_dispose (GObject *object)
{
CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
if (summary->priv->timeout_handle) {
/* this should not happen, because the release timer
* holds a reference on object */
g_source_remove (summary->priv->timeout_handle);
summary->priv->timeout_handle = 0;
}
g_clear_object (&summary->priv->filter_index);
g_clear_object (&summary->priv->filter_64);
g_clear_object (&summary->priv->filter_qp);
g_clear_object (&summary->priv->filter_uu);
g_clear_object (&summary->priv->filter_save);
g_clear_object (&summary->priv->filter_html);
g_clear_object (&summary->priv->filter_stream);
g_clear_object (&summary->priv->filter_index);
if (summary->priv->folder) {
g_object_weak_unref (G_OBJECT (summary->priv->folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
summary->priv->folder = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (camel_folder_summary_parent_class)->dispose (object);
}
static void
folder_summary_finalize (GObject *object)
{
CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
g_hash_table_destroy (summary->priv->uids);
remove_all_loaded (summary);
g_hash_table_destroy (summary->priv->loaded_infos);
g_hash_table_foreach (summary->priv->filter_charset, free_o_name, NULL);
g_hash_table_destroy (summary->priv->filter_charset);
g_rec_mutex_clear (&summary->priv->summary_lock);
g_rec_mutex_clear (&summary->priv->filter_lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_folder_summary_parent_class)->finalize (object);
}
static void
folder_summary_set_folder (CamelFolderSummary *summary,
CamelFolder *folder)
{
g_return_if_fail (summary->priv->folder == NULL);
/* folder can be NULL in certain cases, see maildir-store */
summary->priv->folder = folder;
if (folder)
g_object_weak_ref (G_OBJECT (folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
}
static void
folder_summary_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FOLDER:
folder_summary_set_folder (
CAMEL_FOLDER_SUMMARY (object),
CAMEL_FOLDER (g_value_get_object (value)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
folder_summary_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FOLDER:
g_value_set_object (
value,
camel_folder_summary_get_folder (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_SAVED_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_saved_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_UNREAD_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_unread_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_DELETED_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_deleted_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_JUNK_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_junk_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_JUNK_NOT_DELETED_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_junk_not_deleted_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_VISIBLE_COUNT:
g_value_set_uint (
value,
camel_folder_summary_get_visible_count (
CAMEL_FOLDER_SUMMARY (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static gboolean
is_in_memory_summary (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
return (summary->priv->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0;
}
#define UPDATE_COUNTS_ADD (1)
#define UPDATE_COUNTS_SUB (2)
#define UPDATE_COUNTS_ADD_WITHOUT_TOTAL (3)
#define UPDATE_COUNTS_SUB_WITHOUT_TOTAL (4)
static gboolean
folder_summary_update_counts_by_flags (CamelFolderSummary *summary,
guint32 flags,
gint op_type)
{
gint unread = 0, deleted = 0, junk = 0;
gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
gboolean subtract = op_type == UPDATE_COUNTS_SUB || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
gboolean without_total = op_type == UPDATE_COUNTS_ADD_WITHOUT_TOTAL || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
gboolean changed = FALSE;
GObject *summary_object;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
summary_object = G_OBJECT (summary);
if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
is_junk_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_JUNK;
is_trash_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_TRASH;
} else if (summary->priv->folder) {
guint32 folder_flags;
folder_flags = camel_folder_get_flags (summary->priv->folder);
is_junk_folder = (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
is_trash_folder = (folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
}
if (!(flags & CAMEL_MESSAGE_SEEN))
unread = subtract ? -1 : 1;
if (flags & CAMEL_MESSAGE_DELETED)
deleted = subtract ? -1 : 1;
if (flags & CAMEL_MESSAGE_JUNK)
junk = subtract ? -1 : 1;
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));
g_object_freeze_notify (summary_object);
if (deleted) {
summary->priv->deleted_count += deleted;
g_object_notify (summary_object, "deleted-count");
changed = TRUE;
}
if (junk) {
summary->priv->junk_count += junk;
g_object_notify (summary_object, "junk-count");
changed = TRUE;
}
if (junk && !deleted) {
summary->priv->junk_not_deleted_count += junk;
g_object_notify (summary_object, "junk-not-deleted-count");
changed = TRUE;
}
if (!junk && !deleted) {
summary->priv->visible_count += subtract ? -1 : 1;
g_object_notify (summary_object, "visible-count");
changed = TRUE;
}
if (junk && !is_junk_folder)
unread = 0;
if (deleted && !is_trash_folder)
unread = 0;
if (unread) {
if (unread > 0 || summary->priv->unread_count)
summary->priv->unread_count += unread;
g_object_notify (summary_object, "unread-count");
changed = TRUE;
}
if (!without_total) {
summary->priv->saved_count += subtract ? -1 : 1;
g_object_notify (summary_object, "saved-count");
changed = TRUE;
}
if (changed)
camel_folder_summary_touch (summary);
g_object_thaw_notify (summary_object);
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));
return changed;
}
static gboolean
summary_header_load (CamelFolderSummary *summary,
CamelFIRecord *record)
{
io (printf ("Loading header from db \n"));
summary->priv->version = record->version;
/* We may not worry, as we are setting a new standard here */
#if 0
/* Legacy version check, before version 12 we have no upgrade knowledge */
if ((summary->priv->version > 0xff) && (summary->priv->version & 0xff) < 12) {
io (printf ("Summary header version mismatch"));
errno = EINVAL;
return FALSE;
}
if (!(summary->priv->version < 0x100 && summary->priv->version >= 13))
io (printf ("Loading legacy summary\n"));
else
io (printf ("loading new-format summary\n"));
#endif
summary->priv->flags = record->flags;
summary->priv->nextuid = record->nextuid;
summary->priv->timestamp = record->timestamp;
summary->priv->saved_count = record->saved_count;
summary->priv->unread_count = record->unread_count;
summary->priv->deleted_count = record->deleted_count;
summary->priv->junk_count = record->junk_count;
summary->priv->visible_count = record->visible_count;
summary->priv->junk_not_deleted_count = record->jnd_count;
return TRUE;
}
static CamelFIRecord *
summary_header_save (CamelFolderSummary *summary,
GError **error)
{
CamelFIRecord *record = g_new0 (CamelFIRecord, 1);
CamelStore *parent_store;
CamelDB *cdb;
const gchar *table_name;
/* Though we are going to read, we do this during write,
* so lets use it that way. */
table_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
cdb = parent_store ? camel_store_get_db (parent_store) : NULL;
io (printf ("Savining header to db\n"));
record->folder_name = g_strdup (table_name);
/* we always write out the current version */
record->version = CAMEL_FOLDER_SUMMARY_VERSION;
record->flags = summary->priv->flags;
record->nextuid = summary->priv->nextuid;
record->timestamp = summary->priv->timestamp;
if (cdb && !is_in_memory_summary (summary)) {
/* FIXME: Ever heard of Constructors and initializing ? */
if (camel_db_count_total_message_info (cdb, table_name, &(record->saved_count), NULL))
record->saved_count = 0;
if (camel_db_count_junk_message_info (cdb, table_name, &(record->junk_count), NULL))
record->junk_count = 0;
if (camel_db_count_deleted_message_info (cdb, table_name, &(record->deleted_count), NULL))
record->deleted_count = 0;
if (camel_db_count_unread_message_info (cdb, table_name, &(record->unread_count), NULL))
record->unread_count = 0;
if (camel_db_count_visible_message_info (cdb, table_name, &(record->visible_count), NULL))
record->visible_count = 0;
if (camel_db_count_junk_not_deleted_message_info (cdb, table_name, &(record->jnd_count), NULL))
record->jnd_count = 0;
}
summary->priv->unread_count = record->unread_count;
summary->priv->deleted_count = record->deleted_count;
summary->priv->junk_count = record->junk_count;
summary->priv->visible_count = record->visible_count;
summary->priv->junk_not_deleted_count = record->jnd_count;
return record;
}
/**
* camel_folder_summary_replace_flags:
* @summary: a #CamelFolderSummary
* @info: a #CamelMessageInfo
*
* Updates internal counts based on the flags in @info.
*
* Returns: Whether any count changed
*
* Since: 3.6
**/
gboolean
camel_folder_summary_replace_flags (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
guint32 old_flags, new_flags, added_flags, removed_flags;
gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
GObject *summary_object;
gboolean changed = FALSE;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
g_return_val_if_fail (info != NULL, FALSE);
if (!camel_message_info_get_uid (info) ||
!camel_folder_summary_check_uid (summary, camel_message_info_get_uid (info)))
return FALSE;
summary_object = G_OBJECT (summary);
camel_folder_summary_lock (summary);
g_object_freeze_notify (summary_object);
old_flags = GPOINTER_TO_UINT (g_hash_table_lookup (summary->priv->uids, camel_message_info_get_uid (info)));
new_flags = camel_message_info_get_flags (info);
if ((old_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED) == (new_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED)) {
g_object_thaw_notify (summary_object);
camel_folder_summary_unlock (summary);
return FALSE;
}
if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
is_junk_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_JUNK;
is_trash_folder = vtrash && camel_vtrash_folder_get_folder_type (vtrash) == CAMEL_VTRASH_FOLDER_TRASH;
} else if (summary->priv->folder) {
guint32 folder_flags;
folder_flags = camel_folder_get_flags (summary->priv->folder);
is_junk_folder = (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
is_trash_folder = (folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
}
added_flags = new_flags & (~(old_flags & new_flags));
removed_flags = old_flags & (~(old_flags & new_flags));
if ((old_flags & CAMEL_MESSAGE_SEEN) == (new_flags & CAMEL_MESSAGE_SEEN)) {
/* unread count is different from others, it asks for nonexistence
* of the flag, thus if it wasn't changed, then simply set it
* in added/removed, thus there are no false notifications
* on unread counts */
added_flags |= CAMEL_MESSAGE_SEEN;
removed_flags |= CAMEL_MESSAGE_SEEN;
} else if ((!is_junk_folder && (new_flags & CAMEL_MESSAGE_JUNK) != 0 &&
(old_flags & CAMEL_MESSAGE_JUNK) == (new_flags & CAMEL_MESSAGE_JUNK)) ||
(!is_trash_folder && (new_flags & CAMEL_MESSAGE_DELETED) != 0 &&
(old_flags & CAMEL_MESSAGE_DELETED) == (new_flags & CAMEL_MESSAGE_DELETED))) {
/* The message was set read or unread, but it is a junk or deleted message,
* in a non-Junk/non-Trash folder, thus it doesn't influence an unread count
* there, thus pretend unread didn't change */
added_flags |= CAMEL_MESSAGE_SEEN;
removed_flags |= CAMEL_MESSAGE_SEEN;
}
/* decrement counts with removed flags */
changed = folder_summary_update_counts_by_flags (summary, removed_flags, UPDATE_COUNTS_SUB_WITHOUT_TOTAL) || changed;
/* increment counts with added flags */
changed = folder_summary_update_counts_by_flags (summary, added_flags, UPDATE_COUNTS_ADD_WITHOUT_TOTAL) || changed;
/* update current flags on the summary */
g_hash_table_insert (
summary->priv->uids,
(gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
GUINT_TO_POINTER (new_flags));
g_object_thaw_notify (summary_object);
camel_folder_summary_unlock (summary);
return changed;
}
static void
camel_folder_summary_class_init (CamelFolderSummaryClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->set_property = folder_summary_set_property;
object_class->get_property = folder_summary_get_property;
object_class->dispose = folder_summary_dispose;
object_class->finalize = folder_summary_finalize;
class->message_info_type = CAMEL_TYPE_MESSAGE_INFO_BASE;
class->summary_header_load = summary_header_load;
class->summary_header_save = summary_header_save;
class->message_info_new_from_headers = message_info_new_from_headers;
class->message_info_new_from_parser = message_info_new_from_parser;
class->message_info_new_from_message = message_info_new_from_message;
class->message_info_from_uid = message_info_from_uid;
class->next_uid_string = next_uid_string;
/**
* CamelFolderSummary:folder
*
* The #CamelFolder to which the folder summary belongs.
**/
g_object_class_install_property (
object_class,
PROP_FOLDER,
g_param_spec_object (
"folder",
"Folder",
"The folder to which the folder summary belongs",
CAMEL_TYPE_FOLDER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
/**
* CamelFolderSummary:saved-count
*
* How many infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_SAVED_COUNT,
g_param_spec_uint (
"saved-count",
"Saved count",
"How many infos is savef in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary:unread-count
*
* How many unread infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_UNREAD_COUNT,
g_param_spec_uint (
"unread-count",
"Unread count",
"How many unread infos is saved in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary:deleted-count
*
* How many deleted infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_DELETED_COUNT,
g_param_spec_uint (
"deleted-count",
"Deleted count",
"How many deleted infos is saved in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary:junk-count
*
* How many junk infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_JUNK_COUNT,
g_param_spec_uint (
"junk-count",
"Junk count",
"How many junk infos is saved in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary:junk-not-deleted-count
*
* How many junk and not deleted infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_JUNK_NOT_DELETED_COUNT,
g_param_spec_uint (
"junk-not-deleted-count",
"Junk not deleted count",
"How many junk and not deleted infos is saved in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary:visible-count
*
* How many visible (not deleted and not junk) infos is saved in a summary.
**/
g_object_class_install_property (
object_class,
PROP_VISIBLE_COUNT,
g_param_spec_uint (
"visible-count",
"Visible count",
"How many visible (not deleted and not junk) infos is saved in a summary",
0, G_MAXUINT32,
0, G_PARAM_READABLE));
/**
* CamelFolderSummary::prepare-fetch-all
* @summary: the #CamelFolderSummary which emitted the signal
*
* Emitted on call to camel_folder_summary_prepare_fetch_all().
*
* Since: 3.26
**/
signals[PREPARE_FETCH_ALL] = g_signal_new (
"changed",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelFolderSummaryClass, prepare_fetch_all),
NULL, NULL, NULL,
G_TYPE_NONE, 0,
G_TYPE_NONE);
}
static void
camel_folder_summary_init (CamelFolderSummary *summary)
{
summary->priv = camel_folder_summary_get_instance_private (summary);
summary->priv->version = CAMEL_FOLDER_SUMMARY_VERSION;
summary->priv->flags = 0;
summary->priv->timestamp = 0;
summary->priv->filter_charset = g_hash_table_new (
camel_strcase_hash, camel_strcase_equal);
summary->priv->nextuid = 1;
summary->priv->uids = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
summary->priv->loaded_infos = g_hash_table_new (g_str_hash, g_str_equal);
g_rec_mutex_init (&summary->priv->summary_lock);
g_rec_mutex_init (&summary->priv->filter_lock);
summary->priv->cache_load_time = 0;
summary->priv->timeout_handle = 0;
}
/**
* camel_folder_summary_new:
* @folder: (type CamelFolder): parent #CamelFolder object
*
* Create a new #CamelFolderSummary object.
*
* Returns: a new #CamelFolderSummary object
**/
CamelFolderSummary *
camel_folder_summary_new (CamelFolder *folder)
{
return g_object_new (CAMEL_TYPE_FOLDER_SUMMARY, "folder", folder, NULL);
}
/**
* camel_folder_summary_get_folder:
* @summary: a #CamelFolderSummary object
*
* Returns: (transfer none): a #CamelFolder to which the summary if associated.
*
* Since: 3.4
**/
CamelFolder *
camel_folder_summary_get_folder (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
return summary->priv->folder;
}
/**
* camel_folder_summary_get_flags:
* @summary: a #CamelFolderSummary
*
* Returns: flags of the @summary, a bit-or of #CamelFolderSummaryFlags
*
* Since: 3.24
**/
guint32
camel_folder_summary_get_flags (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->flags;
}
/**
* camel_folder_summary_set_flags:
* @summary: a #CamelFolderSummary
* @flags: flags to set
*
* Sets flags of the @summary, a bit-or of #CamelFolderSummaryFlags.
*
* Since: 3.24
**/
void
camel_folder_summary_set_flags (CamelFolderSummary *summary,
guint32 flags)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
summary->priv->flags = flags;
}
/**
* camel_folder_summary_get_timestamp:
* @summary: a #CamelFolderSummary
*
* Returns: timestamp of the @summary, as set by the descendants
*
* Since: 3.24
**/
gint64
camel_folder_summary_get_timestamp (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->timestamp;
}
/**
* camel_folder_summary_set_timestamp:
* @summary: a #CamelFolderSummary
* @timestamp: a timestamp to set
*
* Sets timestamp of the @summary, provided by the descendants. This doesn't
* change the 'dirty' flag of the @summary.
*
* Since: 3.24
**/
void
camel_folder_summary_set_timestamp (CamelFolderSummary *summary,
gint64 timestamp)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
summary->priv->timestamp = timestamp;
}
/**
* camel_folder_summary_get_version:
* @summary: a #CamelFolderSummary
*
* Returns: version of the @summary
*
* Since: 3.24
**/
guint32
camel_folder_summary_get_version (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->version;
}
/**
* camel_folder_summary_set_version:
* @summary: a #CamelFolderSummary
* @version: version to set
*
* Sets version of the @summary.
*
* Since: 3.24
**/
void
camel_folder_summary_set_version (CamelFolderSummary *summary,
guint32 version)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
summary->priv->version = version;
}
/**
* camel_folder_summary_get_saved_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of saved infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_saved_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->saved_count;
}
/**
* camel_folder_summary_get_unread_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of unread infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_unread_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->unread_count;
}
/**
* camel_folder_summary_get_deleted_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of deleted infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_deleted_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->deleted_count;
}
/**
* camel_folder_summary_get_junk_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of junk infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_junk_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->junk_count;
}
/**
* camel_folder_summary_get_junk_not_deleted_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of junk and not deleted infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_junk_not_deleted_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->junk_not_deleted_count;
}
/**
* camel_folder_summary_get_visible_count:
* @summary: a #CamelFolderSummary object
*
* Returns: Count of visible (not junk and not deleted) infos.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_visible_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return summary->priv->visible_count;
}
/**
* camel_folder_summary_set_index:
* @summary: a #CamelFolderSummary object
* @index: (nullable): a #CamelIndex
*
* Set the index used to index body content. If the index is %NULL, or
* not set (the default), no indexing of body content will take place.
**/
void
camel_folder_summary_set_index (CamelFolderSummary *summary,
CamelIndex *index)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (index != NULL)
g_object_ref (index);
if (summary->priv->index != NULL)
g_object_unref (summary->priv->index);
summary->priv->index = index;
}
/**
* camel_folder_summary_get_index:
* @summary: a #CamelFolderSummary object
*
* Returns: (transfer none) (nullable): a #CamelIndex used to index body content.
*
* Since: 3.4
**/
CamelIndex *
camel_folder_summary_get_index (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
return summary->priv->index;
}
/**
* camel_folder_summary_next_uid:
* @summary: a #CamelFolderSummary object
*
* Generate a new unique uid value as an integer. This
* may be used to create a unique sequence of numbers.
*
* Returns: the next unique uid value
**/
guint32
camel_folder_summary_next_uid (CamelFolderSummary *summary)
{
guint32 uid;
camel_folder_summary_lock (summary);
uid = summary->priv->nextuid++;
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
return uid;
}
/**
* camel_folder_summary_set_next_uid:
* @summary: a #CamelFolderSummary object
* @uid: The next minimum uid to assign. To avoid clashing
* uid's, set this to the uid of a given messages + 1.
*
* Set the next minimum uid available. This can be used to
* ensure new uid's do not clash with existing uid's.
**/
void
camel_folder_summary_set_next_uid (CamelFolderSummary *summary,
guint32 uid)
{
camel_folder_summary_lock (summary);
summary->priv->nextuid = MAX (summary->priv->nextuid, uid);
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_get_next_uid:
* @summary: a #CamelFolderSummary object
*
* Returns: Next uid currently awaiting for assignment. The difference from
* camel_folder_summary_next_uid() is that this function returns actual
* value and doesn't increment it before returning.
*
* Since: 3.4
**/
guint32
camel_folder_summary_get_next_uid (CamelFolderSummary *summary)
{
guint32 res;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
camel_folder_summary_lock (summary);
res = summary->priv->nextuid;
camel_folder_summary_unlock (summary);
return res;
}
/**
* camel_folder_summary_next_uid_string:
* @summary: a #CamelFolderSummary object
*
* Retrieve the next uid, but as a formatted string.
*
* Returns: the next uid as an unsigned integer string.
* This string must be freed by the caller.
**/
gchar *
camel_folder_summary_next_uid_string (CamelFolderSummary *summary)
{
CamelFolderSummaryClass *class;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (class != NULL, NULL);
g_return_val_if_fail (class->next_uid_string != NULL, NULL);
return class->next_uid_string (summary);
}
/**
* camel_folder_summary_count:
* @summary: a #CamelFolderSummary object
*
* Get the number of summary items stored in this summary.
*
* Returns: the number of items in the summary
**/
guint
camel_folder_summary_count (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
return g_hash_table_size (summary->priv->uids);
}
/**
* camel_folder_summary_check_uid
* @summary: a #CamelFolderSummary object
* @uid: a uid
*
* Check if the uid is valid. This isn't very efficient, so it shouldn't be called iteratively.
*
*
* Returns: if the uid is present in the summary or not (%TRUE or %FALSE)
*
* Since: 2.24
**/
gboolean
camel_folder_summary_check_uid (CamelFolderSummary *summary,
const gchar *uid)
{
gboolean ret;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
g_return_val_if_fail (uid != NULL, FALSE);
camel_folder_summary_lock (summary);
ret = g_hash_table_contains (summary->priv->uids, uid);
camel_folder_summary_unlock (summary);
return ret;
}
static void
folder_summary_dupe_uids_to_array (gpointer key_uid,
gpointer value_flags,
gpointer user_data)
{
g_ptr_array_add (user_data, (gpointer) camel_pstring_strdup (key_uid));
}
/**
* camel_folder_summary_get_array:
* @summary: a #CamelFolderSummary object
*
* Obtain a copy of the summary array. This is done atomically,
* so cannot contain empty entries.
*
* Free with camel_folder_summary_free_array()
*
* Returns: (element-type utf8) (transfer full): a #GPtrArray of uids
*
* Since: 3.4
**/
GPtrArray *
camel_folder_summary_get_array (CamelFolderSummary *summary)
{
GPtrArray *res;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
camel_folder_summary_lock (summary);
/* Do not set free_func on the array, it would break IMAPx code */
res = g_ptr_array_sized_new (g_hash_table_size (summary->priv->uids));
g_hash_table_foreach (summary->priv->uids, folder_summary_dupe_uids_to_array, res);
camel_folder_summary_unlock (summary);
return res;
}
/**
* camel_folder_summary_free_array:
* @array: (element-type utf8): a #GPtrArray returned from camel_folder_summary_get_array()
*
* Free's array and its elements returned from camel_folder_summary_get_array().
*
* Since: 3.4
**/
void
camel_folder_summary_free_array (GPtrArray *array)
{
if (!array)
return;
g_ptr_array_foreach (array, (GFunc) camel_pstring_free, NULL);
g_ptr_array_free (array, TRUE);
}
static void
cfs_copy_uids_cb (gpointer key,
gpointer value,
gpointer user_data)
{
const gchar *uid = key;
GHashTable *copy_hash = user_data;
g_hash_table_insert (copy_hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
}
/**
* camel_folder_summary_get_hash:
* @summary: a #CamelFolderSummary object
*
* Returns hash of current stored 'uids' in summary, where key is 'uid'
* from the string pool, and value is 1. The returned pointer should
* be freed with g_hash_table_destroy().
*
* Note: When searching for values always use uids from the string pool.
*
* Returns: (element-type utf8 gint) (transfer container):
*
* Since: 3.6
**/
GHashTable *
camel_folder_summary_get_hash (CamelFolderSummary *summary)
{
GHashTable *uids;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
camel_folder_summary_lock (summary);
/* using direct hash because of strings being from the string pool */
uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
g_hash_table_foreach (summary->priv->uids, cfs_copy_uids_cb, uids);
camel_folder_summary_unlock (summary);
return uids;
}
/**
* camel_folder_summary_peek_loaded:
* @summary: a #CamelFolderSummary
* @uid: a message UID to look for
*
* Returns: (nullable) (transfer full): a #CamelMessageInfo for the given @uid,
* if it's currently loaded in memory, or %NULL otherwise. Unref the non-NULL
* info with g_object_unref() when done with it.
*
* Since: 2.26
**/
CamelMessageInfo *
camel_folder_summary_peek_loaded (CamelFolderSummary *summary,
const gchar *uid)
{
CamelMessageInfo *info;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
camel_folder_summary_lock (summary);
info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
if (info)
g_object_ref (info);
camel_folder_summary_unlock (summary);
return info;
}
struct _db_pass_data {
GHashTable *columns_hash;
CamelFolderSummary *summary;
gboolean add; /* or just insert to hashtable */
};
static CamelMessageInfo *
message_info_from_uid (CamelFolderSummary *summary,
const gchar *uid)
{
CamelMessageInfo *info;
gint ret;
camel_folder_summary_lock (summary);
info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
if (!info) {
CamelDB *cdb;
CamelStore *parent_store;
const gchar *folder_name;
struct _db_pass_data data;
folder_name = camel_folder_get_full_name (summary->priv->folder);
if (is_in_memory_summary (summary)) {
camel_folder_summary_unlock (summary);
g_warning (
"%s: Tried to load uid '%s' "
"from DB on in-memory summary of '%s'",
G_STRFUNC, uid, folder_name);
return NULL;
}
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store) {
camel_folder_summary_unlock (summary);
return NULL;
}
cdb = camel_store_get_db (parent_store);
data.columns_hash = NULL;
data.summary = summary;
data.add = FALSE;
ret = camel_db_read_message_info_record_with_uid (
cdb, folder_name, uid, &data,
camel_read_mir_callback, NULL);
if (data.columns_hash)
g_hash_table_destroy (data.columns_hash);
if (ret != 0) {
camel_folder_summary_unlock (summary);
return NULL;
}
/* We would have double reffed at camel_read_mir_callback */
info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
cfs_schedule_info_release_timer (summary);
}
if (info)
g_object_ref (info);
camel_folder_summary_unlock (summary);
return info;
}
/**
* camel_folder_summary_get: (virtual message_info_from_uid)
* @summary: a #CamelFolderSummary object
* @uid: a uid
*
* Retrieve a summary item by uid.
*
* A referenced to the summary item is returned, which may be
* ref'd or free'd as appropriate.
*
* Returns: (nullable) (transfer full): the summary item, or %NULL if the uid @uid is not available
*
* See camel_folder_summary_get_info_flags().
*
* Since: 3.4
**/
CamelMessageInfo *
camel_folder_summary_get (CamelFolderSummary *summary,
const gchar *uid)
{
CamelFolderSummaryClass *class;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
g_return_val_if_fail (uid != NULL, NULL);
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (class != NULL, NULL);
g_return_val_if_fail (class->message_info_from_uid != NULL, NULL);
return class->message_info_from_uid (summary, uid);
}
/**
* camel_folder_summary_get_info_flags:
* @summary: a #CamelFolderSummary object
* @uid: a uid
*
* Retrieve CamelMessageInfo::flags for a message info with UID @uid.
* This is much quicker than camel_folder_summary_get(), because it
* doesn't require reading the message info from a disk.
*
* Returns: the flags currently stored for message info with UID @uid,
* or (~0) on error
*
* Since: 3.12
**/
guint32
camel_folder_summary_get_info_flags (CamelFolderSummary *summary,
const gchar *uid)
{
gpointer ptr_uid = NULL, ptr_flags = NULL;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), (~0));
g_return_val_if_fail (uid != NULL, (~0));
camel_folder_summary_lock (summary);
if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
camel_folder_summary_unlock (summary);
return (~0);
}
camel_folder_summary_unlock (summary);
return GPOINTER_TO_UINT (ptr_flags);
}
static void
gather_dirty_or_flagged_uids (gpointer key,
gpointer value,
gpointer user_data)
{
const gchar *uid = key;
CamelMessageInfo *info = value;
GHashTable *hash = user_data;
if (camel_message_info_get_dirty (info) || (camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
}
static void
gather_changed_uids (gpointer key,
gpointer value,
gpointer user_data)
{
const gchar *uid = key;
guint32 flags = GPOINTER_TO_UINT (value);
GHashTable *hash = user_data;
if ((flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
}
/**
* camel_folder_summary_get_changed:
* @summary: a #CamelFolderSummary
*
* Returns an array of changed UID-s. A UID is considered changed
* when its corresponding CamelMesageInfo is 'dirty' or when it has
* set the #CAMEL_MESSAGE_FOLDER_FLAGGED flag.
*
* Returns: (element-type utf8) (transfer full): a #GPtrArray with changed UID-s.
* Free it with camel_folder_summary_free_array() when no longer needed.
*
* Since: 2.24
**/
GPtrArray *
camel_folder_summary_get_changed (CamelFolderSummary *summary)
{
GPtrArray *res;
GHashTable *hash;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
camel_folder_summary_lock (summary);
g_hash_table_foreach (summary->priv->loaded_infos, gather_dirty_or_flagged_uids, hash);
g_hash_table_foreach (summary->priv->uids, gather_changed_uids, hash);
res = g_ptr_array_sized_new (g_hash_table_size (hash));
g_hash_table_foreach (hash, folder_summary_dupe_uids_to_array, res);
camel_folder_summary_unlock (summary);
g_hash_table_destroy (hash);
return res;
}
static void
count_changed_uids (gchar *key,
CamelMessageInfo *info,
gint *count)
{
if (camel_message_info_get_dirty (info))
(*count)++;
}
static gint
cfs_count_dirty (CamelFolderSummary *summary)
{
gint count = 0;
camel_folder_summary_lock (summary);
g_hash_table_foreach (summary->priv->loaded_infos, (GHFunc) count_changed_uids, &count);
camel_folder_summary_unlock (summary);
return count;
}
static gboolean
remove_item (gchar *uid,
CamelMessageInfo *info,
GSList **to_remove_infos)
{
if (G_OBJECT (info)->ref_count == 1 && !camel_message_info_get_dirty (info) && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
*to_remove_infos = g_slist_prepend (*to_remove_infos, info);
return TRUE;
}
return FALSE;
}
static void
remove_cache (CamelSession *session,
GCancellable *cancellable,
CamelFolderSummary *summary,
GError **error)
{
GSList *to_remove_infos = NULL;
camel_db_release_cache_memory ();
if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP)
return;
camel_folder_summary_lock (summary);
g_hash_table_foreach_remove (summary->priv->loaded_infos, (GHRFunc) remove_item, &to_remove_infos);
g_slist_free_full (to_remove_infos, g_object_unref);
camel_folder_summary_unlock (summary);
summary->priv->cache_load_time = time (NULL);
}
static void
cfs_free_weakref (gpointer ptr)
{
GWeakRef *weakref = ptr;
if (weakref) {
g_weak_ref_set (weakref, NULL);
g_weak_ref_clear (weakref);
g_slice_free (GWeakRef, weakref);
}
}
static gboolean
cfs_try_release_memory (gpointer user_data)
{
GWeakRef *weakref = user_data;
CamelFolderSummary *summary;
CamelStore *parent_store;
CamelSession *session;
gchar *description;
g_return_val_if_fail (weakref != NULL, FALSE);
summary = g_weak_ref_get (weakref);
if (!summary)
return FALSE;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
/* If folder is freed or if the cache is nil then clean up */
if (!summary->priv->folder ||
!g_hash_table_size (summary->priv->loaded_infos) ||
is_in_memory_summary (summary)) {
summary->priv->cache_load_time = 0;
summary->priv->timeout_handle = 0;
g_object_unref (summary);
return FALSE;
}
if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP) {
g_object_unref (summary);
return TRUE;
}
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store) {
summary->priv->cache_load_time = 0;
summary->priv->timeout_handle = 0;
g_object_unref (summary);
return FALSE;
}
session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
if (!session) {
summary->priv->cache_load_time = 0;
summary->priv->timeout_handle = 0;
g_object_unref (summary);
return FALSE;
}
/* Translators: The first “%s” is replaced with an account name and the second “%s”
is replaced with a full path name. The spaces around “:” are intentional, as
the whole “%s : %s” is meant as an absolute identification of the folder. */
description = g_strdup_printf (_("Release unused memory for folder “%s : %s”"),
camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
camel_folder_get_full_display_name (summary->priv->folder));
camel_session_submit_job (
session, description,
(CamelSessionCallback) remove_cache,
/* Consumes the reference of the 'summary'. */
summary, g_object_unref);
g_object_unref (session);
g_free (description);
return TRUE;
}
static void
cfs_schedule_info_release_timer (CamelFolderSummary *summary)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (is_in_memory_summary (summary))
return;
if (!summary->priv->timeout_handle) {
static gboolean know_can_do = FALSE, can_do = TRUE;
if (!know_can_do) {
can_do = !g_getenv ("CAMEL_FREE_INFOS");
know_can_do = TRUE;
}
/* FIXME[disk-summary] LRU please and not timeouts */
if (can_do) {
GWeakRef *weakref;
GSource *source;
weakref = g_slice_new0 (GWeakRef);
g_weak_ref_init (weakref, summary);
source = g_timeout_source_new_seconds (SUMMARY_CACHE_DROP);
g_source_set_callback (source, cfs_try_release_memory, weakref, cfs_free_weakref);
g_source_set_name (source, "[camel] cfs_try_release_memory");
summary->priv->timeout_handle = g_source_attach (source, NULL);
g_source_unref (source);
}
}
/* update also cache load time to the actual, to not release something just loaded */
summary->priv->cache_load_time = time (NULL);
}
static gint
cfs_cache_size (CamelFolderSummary *summary)
{
/* FIXME[disk-summary] this is a timely hack. fix it well */
if (!CAMEL_IS_VEE_FOLDER (summary->priv->folder))
return g_hash_table_size (summary->priv->loaded_infos);
else
return g_hash_table_size (summary->priv->uids);
}
static void
cfs_reload_from_db (CamelFolderSummary *summary,
GError **error)
{
CamelDB *cdb;
CamelStore *parent_store;
const gchar *folder_name;
struct _db_pass_data data;
/* FIXME[disk-summary] baseclass this, and vfolders we may have to
* load better. */
d (printf ("\ncamel_folder_summary_reload_from_db called \n"));
if (is_in_memory_summary (summary))
return;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store)
return;
folder_name = camel_folder_get_full_name (summary->priv->folder);
cdb = camel_store_get_db (parent_store);
data.columns_hash = NULL;
data.summary = summary;
data.add = FALSE;
camel_db_read_message_info_records (
cdb, folder_name, (gpointer) &data,
camel_read_mir_callback, NULL);
if (data.columns_hash)
g_hash_table_destroy (data.columns_hash);
cfs_schedule_info_release_timer (summary);
}
/**
* camel_folder_summary_prepare_fetch_all:
* @summary: #CamelFolderSummary object
* @error: return location for a #GError, or %NULL
*
* Loads all infos into memory, if they are not yet and ensures
* they will not be freed in next couple minutes. Call this function
* before any mass operation or when all message infos will be needed,
* for better performance.
*
* Since: 2.32
**/
void
camel_folder_summary_prepare_fetch_all (CamelFolderSummary *summary,
GError **error)
{
guint loaded, known;
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
loaded = cfs_cache_size (summary);
known = camel_folder_summary_count (summary);
g_signal_emit (summary, signals[PREPARE_FETCH_ALL], 0);
if (known - loaded > 50) {
camel_folder_summary_lock (summary);
cfs_reload_from_db (summary, error);
camel_folder_summary_unlock (summary);
}
/* update also cache load time, even when not loaded anything */
summary->priv->cache_load_time = time (NULL);
}
/**
* camel_folder_summary_load:
* @summary: a #CamelFolderSummary
* @error: return location for a #GError, or %NULL
*
* Loads the summary from the disk. It also saves any pending
* changes first.
*
* Returns: whether succeeded
*
* Since: 3.24
**/
gboolean
camel_folder_summary_load (CamelFolderSummary *summary,
GError **error)
{
CamelFolderSummaryClass *klass;
CamelDB *cdb;
CamelStore *parent_store;
const gchar *full_name;
gint ret = 0;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, FALSE);
if (is_in_memory_summary (summary))
return TRUE;
camel_folder_summary_lock (summary);
camel_folder_summary_save (summary, NULL);
/* struct _db_pass_data data; */
d (printf ("\ncamel_folder_summary_load called \n"));
full_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!camel_folder_summary_header_load (summary, parent_store, full_name, error)) {
camel_folder_summary_unlock (summary);
return FALSE;
}
if (!parent_store) {
camel_folder_summary_unlock (summary);
return FALSE;
}
cdb = camel_store_get_db (parent_store);
ret = camel_db_prepare_message_info_table (cdb, full_name, error);
if (ret == 0)
ret = camel_db_get_folder_uids (cdb, full_name, klass->sort_by, klass->collate, summary->priv->uids, error);
camel_folder_summary_unlock (summary);
return ret == 0;
}
/* Beware, it only borrows pointers from 'cols' here */
static void
mir_from_cols (CamelMIRecord *mir,
CamelFolderSummary *summary,
GHashTable **columns_hash,
gint ncol,
gchar **cols,
gchar **name)
{
gint i;
for (i = 0; i < ncol; ++i) {
if (!name[i] || !cols[i])
continue;
switch (camel_db_get_column_ident (columns_hash, i, ncol, name)) {
case CAMEL_DB_COLUMN_UID:
mir->uid = cols[i];
break;
case CAMEL_DB_COLUMN_FLAGS:
mir->flags = cols[i] ? g_ascii_strtoull (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_READ:
mir->read = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_DELETED:
mir->deleted = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_REPLIED:
mir->replied = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_IMPORTANT:
mir->important = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_JUNK:
mir->junk = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_ATTACHMENT:
mir->attachment = (cols[i]) ? ( ((g_ascii_strtoull (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_SIZE:
mir->size = cols[i] ? g_ascii_strtoull (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_DSENT:
mir->dsent = cols[i] ? g_ascii_strtoll (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_DRECEIVED:
mir->dreceived = cols[i] ? g_ascii_strtoll (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_SUBJECT:
mir->subject = cols[i];
break;
case CAMEL_DB_COLUMN_MAIL_FROM:
mir->from = cols[i];
break;
case CAMEL_DB_COLUMN_MAIL_TO:
mir->to = cols[i];
break;
case CAMEL_DB_COLUMN_MAIL_CC:
mir->cc = cols[i];
break;
case CAMEL_DB_COLUMN_MLIST:
mir->mlist = cols[i];
break;
case CAMEL_DB_COLUMN_FOLLOWUP_FLAG:
mir->followup_flag = cols[i];
break;
case CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON:
mir->followup_completed_on = cols[i];
break;
case CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY:
mir->followup_due_by = cols[i];
break;
case CAMEL_DB_COLUMN_PART:
mir->part = cols[i];
break;
case CAMEL_DB_COLUMN_LABELS:
mir->labels = cols[i];
break;
case CAMEL_DB_COLUMN_USERTAGS:
mir->usertags = cols[i];
break;
case CAMEL_DB_COLUMN_CINFO:
mir->cinfo = cols[i];
break;
case CAMEL_DB_COLUMN_BDATA:
mir->bdata = cols[i];
break;
case CAMEL_DB_COLUMN_USERHEADERS:
mir->userheaders = cols[i];
break;
case CAMEL_DB_COLUMN_PREVIEW:
mir->preview = cols[i];
break;
default:
g_warn_if_reached ();
break;
}
}
}
static gint
camel_read_mir_callback (gpointer ref,
gint ncol,
gchar **cols,
gchar **name)
{
struct _db_pass_data *data = (struct _db_pass_data *) ref;
CamelFolderSummary *summary = data->summary;
CamelMIRecord mir;
CamelMessageInfo *info;
gchar *bdata_ptr;
gint ret = 0;
memset (&mir, 0, sizeof (CamelMIRecord));
/* As mir_from_cols() only borrows data from cols, no need to free mir */
mir_from_cols (&mir, summary, &data->columns_hash, ncol, cols, name);
camel_folder_summary_lock (summary);
if (!mir.uid || g_hash_table_lookup (summary->priv->loaded_infos, mir.uid)) {
/* Unlock and better return */
camel_folder_summary_unlock (summary);
return ret;
}
camel_folder_summary_unlock (summary);
info = camel_message_info_new (summary);
bdata_ptr = mir.bdata;
if (camel_message_info_load (info, &mir, &bdata_ptr)) {
/* Just now we are reading from the DB, it can't be dirty. */
camel_message_info_set_dirty (info, FALSE);
if (data->add) {
camel_folder_summary_add (summary, info, TRUE);
g_clear_object (&info);
} else {
camel_folder_summary_lock (summary);
/* Summary always holds a ref for the loaded infos; this consumes it */
g_hash_table_insert (summary->priv->loaded_infos, (gchar *) camel_message_info_get_uid (info), info);
camel_folder_summary_unlock (summary);
}
} else {
g_clear_object (&info);
g_warning ("Loading messageinfo from db failed");
ret = -1;
}
return ret;
}
typedef struct _SaveData {
CamelFolderSummary *summary;
const gchar *full_name;
CamelDB *cdb;
GError **out_error;
} SaveData;
static void
save_to_db_cb (gpointer key,
gpointer value,
gpointer user_data)
{
CamelMessageInfo *mi = value;
CamelMIRecord *mir;
GString *bdata_str;
SaveData *dt = user_data;
g_return_if_fail (dt != NULL);
if (!camel_message_info_get_dirty (mi))
return;
mir = g_new0 (CamelMIRecord, 1);
bdata_str = g_string_new (NULL);
if (!camel_message_info_save (mi, mir, bdata_str)) {
g_warning ("Failed to save message info: %s\n", camel_message_info_get_uid (mi));
g_string_free (bdata_str, TRUE);
camel_db_camel_mir_free (mir);
return;
}
g_warn_if_fail (mir->bdata == NULL);
mir->bdata = g_string_free (bdata_str, FALSE);
bdata_str = NULL;
if (camel_db_write_message_info_record (dt->cdb, dt->full_name, mir, dt->out_error) != 0) {
camel_db_camel_mir_free (mir);
return;
}
/* Reset the dirty flag which decides if the changes are synced to the DB or not.
The FOLDER_FLAGGED should be used to check if the changes are synced to the server.
So, don't unset the FOLDER_FLAGGED flag */
camel_message_info_set_dirty (mi, FALSE);
camel_db_camel_mir_free (mir);
}
static gint
save_message_infos_to_db (CamelFolderSummary *summary,
GError **error)
{
CamelStore *parent_store;
CamelDB *cdb;
const gchar *full_name;
SaveData dt;
if (is_in_memory_summary (summary))
return 0;
full_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store)
return 0;
cdb = camel_store_get_db (parent_store);
if (camel_db_prepare_message_info_table (cdb, full_name, error) != 0)
return -1;
camel_folder_summary_lock (summary);
dt.summary = summary;
dt.full_name = full_name;
dt.cdb = cdb;
dt.out_error = error;
/* Push MessageInfo-es */
camel_db_begin_transaction (cdb, NULL);
g_hash_table_foreach (summary->priv->loaded_infos, save_to_db_cb, &dt);
camel_db_end_transaction (cdb, NULL);
camel_folder_summary_unlock (summary);
cfs_schedule_info_release_timer (summary);
return 0;
}
/**
* camel_folder_summary_save:
* @summary: a #CamelFolderSummary
* @error: return location for a #GError, or %NULL
*
* Saves the content of the @summary to disk. It does nothing,
* when the summary is not changed or when it doesn't support
* permanent save.
*
* Returns: whether succeeded
*
* Since: 3.24
**/
gboolean
camel_folder_summary_save (CamelFolderSummary *summary,
GError **error)
{
CamelFolderSummaryClass *klass;
CamelStore *parent_store;
CamelDB *cdb;
CamelFIRecord *record;
gint ret, count;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, FALSE);
g_return_val_if_fail (klass->summary_header_save != NULL, FALSE);
if (!(summary->priv->flags & CAMEL_FOLDER_SUMMARY_DIRTY) ||
is_in_memory_summary (summary))
return TRUE;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store)
return FALSE;
cdb = camel_store_get_db (parent_store);
camel_folder_summary_lock (summary);
d (printf ("\ncamel_folder_summary_save called \n"));
summary->priv->flags &= ~CAMEL_FOLDER_SUMMARY_DIRTY;
count = cfs_count_dirty (summary);
if (!count) {
gboolean res = camel_folder_summary_header_save (summary, error);
camel_folder_summary_unlock (summary);
return res;
}
ret = save_message_infos_to_db (summary, error);
if (ret != 0) {
/* Failed, so lets reset the flag */
summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
return FALSE;
}
/* XXX So... if an error is set, how do we even reach this point
* given the above error check? Oye vey this logic is nasty. */
if (error != NULL && *error != NULL &&
strstr ((*error)->message, "26 columns but 28 values") != NULL) {
const gchar *full_name;
full_name = camel_folder_get_full_name (summary->priv->folder);
g_warning ("Fixing up a broken summary migration on '%s : %s'\n",
camel_service_get_display_name (CAMEL_SERVICE (parent_store)), full_name);
/* Begin everything again. */
camel_db_begin_transaction (cdb, NULL);
camel_db_reset_folder_version (cdb, full_name, 0, NULL);
camel_db_end_transaction (cdb, NULL);
ret = save_message_infos_to_db (summary, error);
if (ret != 0) {
summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
return FALSE;
}
}
record = klass->summary_header_save (summary, error);
if (!record) {
summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
return FALSE;
}
camel_db_begin_transaction (cdb, NULL);
ret = camel_db_write_folder_info_record (cdb, record, error);
g_free (record->folder_name);
g_free (record->bdata);
g_free (record);
if (ret != 0) {
camel_db_abort_transaction (cdb, NULL);
summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
return FALSE;
}
camel_db_end_transaction (cdb, NULL);
camel_folder_summary_unlock (summary);
return ret == 0;
}
/**
* camel_folder_summary_header_save:
* @summary: a #CamelFolderSummary
* @error: return location for a #GError, or %NULL
*
* Saves summary header information into the disk. The function does
* nothing, if the summary doesn't support save to disk.
*
* Returns: whether succeeded
*
* Since: 3.24
**/
gboolean
camel_folder_summary_header_save (CamelFolderSummary *summary,
GError **error)
{
CamelFolderSummaryClass *klass;
CamelStore *parent_store;
CamelFIRecord *record;
CamelDB *cdb;
gint ret;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, FALSE);
g_return_val_if_fail (klass->summary_header_save != NULL, FALSE);
if (is_in_memory_summary (summary))
return TRUE;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store)
return FALSE;
cdb = camel_store_get_db (parent_store);
camel_folder_summary_lock (summary);
d (printf ("\ncamel_folder_summary_header_save called \n"));
record = klass->summary_header_save (summary, error);
if (!record) {
camel_folder_summary_unlock (summary);
return FALSE;
}
camel_db_begin_transaction (cdb, NULL);
ret = camel_db_write_folder_info_record (cdb, record, error);
g_free (record->folder_name);
g_free (record->bdata);
g_free (record);
if (ret != 0) {
camel_db_abort_transaction (cdb, NULL);
camel_folder_summary_unlock (summary);
return FALSE;
}
camel_db_end_transaction (cdb, NULL);
camel_folder_summary_unlock (summary);
return ret == 0;
}
/**
* camel_folder_summary_header_load:
* @summary: a #CamelFolderSummary
* @store: a #CamelStore
* @folder_name: a folder name corresponding to @summary
* @error: return location for a #GError, or %NULL
*
* Loads a summary header for the @summary, which corresponds to @folder_name
* provided by @store.
*
* Returns: whether succeeded
*
* Since: 3.24
**/
gboolean
camel_folder_summary_header_load (CamelFolderSummary *summary,
CamelStore *store,
const gchar *folder_name,
GError **error)
{
CamelFolderSummaryClass *klass;
CamelDB *cdb;
CamelFIRecord *record;
gboolean ret = FALSE;
d (printf ("\ncamel_folder_summary_header_load called \n"));
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, FALSE);
g_return_val_if_fail (klass->summary_header_load != NULL, FALSE);
if (is_in_memory_summary (summary))
return TRUE;
camel_folder_summary_lock (summary);
camel_folder_summary_save (summary, NULL);
cdb = camel_store_get_db (store);
record = g_new0 (CamelFIRecord, 1);
camel_db_read_folder_info_record (cdb, folder_name, record, error);
ret = klass->summary_header_load (summary, record);
camel_folder_summary_unlock (summary);
g_free (record->folder_name);
g_free (record->bdata);
g_free (record);
return ret;
}
static gboolean
summary_assign_uid (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
const gchar *info_uid;
gchar *new_uid;
CamelMessageInfo *mi;
camel_message_info_set_abort_notifications (info, TRUE);
camel_message_info_property_lock (info);
info_uid = camel_message_info_get_uid (info);
if (!info_uid || !*info_uid) {
new_uid = camel_folder_summary_next_uid_string (summary);
camel_message_info_set_uid (info, new_uid);
} else {
new_uid = g_strdup (info_uid);
}
camel_folder_summary_lock (summary);
while ((mi = g_hash_table_lookup (summary->priv->loaded_infos, new_uid))) {
camel_folder_summary_unlock (summary);
g_free (new_uid);
if (mi == info) {
camel_message_info_property_unlock (info);
camel_message_info_set_abort_notifications (info, FALSE);
return FALSE;
}
d (printf ("Trying to insert message with clashing uid (%s). new uid re-assigned", camel_message_info_get_uid (info)));
new_uid = camel_folder_summary_next_uid_string (summary);
camel_message_info_set_uid (info, new_uid);
camel_message_info_set_folder_flagged (info, TRUE);
camel_folder_summary_lock (summary);
}
g_free (new_uid);
camel_folder_summary_unlock (summary);
camel_message_info_property_unlock (info);
camel_message_info_set_abort_notifications (info, FALSE);
return TRUE;
}
/**
* camel_folder_summary_add:
* @summary: a #CamelFolderSummary object
* @info: a #CamelMessageInfo
* @force_keep_uid: whether to keep set UID of the @info
*
* Adds a new @info record to the summary. If the @force_keep_uid is %FALSE,
* then a new uid is automatically re-assigned by calling
* camel_folder_summary_next_uid_string(). It's an error to use
* @force_keep_uid when the @info has none set.
*
* The @summary adds its own reference to @info, if needed, and any
* previously loaded info is replaced with the new one.
**/
void
camel_folder_summary_add (CamelFolderSummary *summary,
CamelMessageInfo *info,
gboolean force_keep_uid)
{
CamelMessageInfo *loaded_info;
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (!info)
return;
g_return_if_fail (CAMEL_IS_MESSAGE_INFO (info));
camel_folder_summary_lock (summary);
if (!force_keep_uid && !summary_assign_uid (summary, info)) {
camel_folder_summary_unlock (summary);
return;
}
if (force_keep_uid) {
const gchar *uid;
uid = camel_message_info_get_uid (info);
if (!uid || !*uid) {
g_warning ("%s: Cannot add message info without UID, when disabled to assign new UID; skipping it", G_STRFUNC);
camel_folder_summary_unlock (summary);
return;
}
}
folder_summary_update_counts_by_flags (summary, camel_message_info_get_flags (info), UPDATE_COUNTS_ADD);
camel_message_info_set_folder_flagged (info, TRUE);
camel_message_info_set_dirty (info, TRUE);
g_hash_table_insert (
summary->priv->uids,
(gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
GUINT_TO_POINTER (camel_message_info_get_flags (info)));
/* Summary always holds a ref for the loaded infos */
g_object_ref (info);
loaded_info = g_hash_table_lookup (summary->priv->loaded_infos, camel_message_info_get_uid (info));
if (loaded_info) {
/* Dirty hack, to have CamelWeakRefGroup properly cleared,
when the message info leaks due to ref/unref imbalance. */
_camel_message_info_unset_summary (loaded_info);
g_clear_object (&loaded_info);
}
g_hash_table_insert (summary->priv->loaded_infos, (gpointer) camel_message_info_get_uid (info), info);
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_info_new_from_headers: (virtual message_info_new_from_headers)
* @summary: a #CamelFolderSummary object
* @headers: rfc822 headers as #CamelNameValueArray
*
* Create a new info record from a header.
*
* Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
* with g_object_unref(), when done with it.
*
* Since: 3.24
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_headers (CamelFolderSummary *summary,
const CamelNameValueArray *headers)
{
CamelFolderSummaryClass *class;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (class != NULL, NULL);
g_return_val_if_fail (class->message_info_new_from_headers != NULL, NULL);
return class->message_info_new_from_headers (summary, headers);
}
/**
* camel_folder_summary_info_new_from_parser: (virtual message_info_new_from_parser)
* @summary: a #CamelFolderSummary object
* @parser: a #CamelMimeParser object
*
* Create a new info record from a parser. If the parser cannot
* determine a uid, then none will be assigned.
*
* If indexing is enabled, and the parser cannot determine a new uid, then
* one is automatically assigned.
*
* If indexing is enabled, then the content will be indexed based
* on this new uid. In this case, the message info MUST be
* added using :add().
*
* Once complete, the parser will be positioned at the end of
* the message.
*
* Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
* with g_object_unref(), when done with it.
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_parser (CamelFolderSummary *summary,
CamelMimeParser *mp)
{
CamelFolderSummaryClass *klass;
CamelMessageInfo *info = NULL;
gchar *buffer;
gsize len;
goffset start;
CamelIndexName *name = NULL;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), NULL);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (klass->message_info_new_from_parser, NULL);
/* should this check the parser is in the right state, or assume it is?? */
start = camel_mime_parser_tell (mp);
if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_EOF) {
info = klass->message_info_new_from_parser (summary, mp);
camel_mime_parser_unstep (mp);
/* assign a unique uid, this is slightly 'wrong' as we do not really
* know if we are going to store this in the summary, but no matter */
if (summary->priv->index)
summary_assign_uid (summary, info);
g_rec_mutex_lock (&summary->priv->filter_lock);
if (summary->priv->index) {
if (!summary->priv->filter_index)
summary->priv->filter_index = camel_mime_filter_index_new (summary->priv->index);
camel_index_delete_name (summary->priv->index, camel_message_info_get_uid (info));
name = camel_index_add_name (summary->priv->index, camel_message_info_get_uid (info));
camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), name);
}
/* always scan the content info, even if we don't save it */
summary_traverse_content_with_parser (summary, info, mp);
if (name && summary->priv->index) {
camel_index_write_name (summary->priv->index, name);
g_object_unref (name);
camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), NULL);
}
g_rec_mutex_unlock (&summary->priv->filter_lock);
camel_message_info_set_size (info, camel_mime_parser_tell (mp) - start);
}
return info;
}
/**
* camel_folder_summary_info_new_from_message: (virtual message_info_new_from_message)
* @summary: a #CamelFolderSummary object
* @message: a #CamelMimeMessage object
*
* Create a summary item from a message.
*
* Returns: (transfer full): a newly created #CamelMessageInfo. Unref it
* with g_object_unref(), when done with it.
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_message (CamelFolderSummary *summary,
CamelMimeMessage *msg)
{
CamelFolderSummaryClass *klass;
CamelMessageInfo *info;
CamelIndexName *name = NULL;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (msg), NULL);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (klass->message_info_new_from_message != NULL, NULL);
info = klass->message_info_new_from_message (summary, msg);
/* assign a unique uid, this is slightly 'wrong' as we do not really
* know if we are going to store this in the summary, but we need it set for indexing */
if (summary->priv->index)
summary_assign_uid (summary, info);
g_rec_mutex_lock (&summary->priv->filter_lock);
if (summary->priv->index) {
if (summary->priv->filter_index == NULL)
summary->priv->filter_index = camel_mime_filter_index_new (summary->priv->index);
camel_index_delete_name (summary->priv->index, camel_message_info_get_uid (info));
name = camel_index_add_name (summary->priv->index, camel_message_info_get_uid (info));
camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), name);
if (!summary->priv->filter_stream) {
CamelStream *null = camel_stream_null_new ();
summary->priv->filter_stream = camel_stream_filter_new (null);
g_object_unref (null);
}
}
summary_traverse_content_with_part (summary, info, (CamelMimePart *) msg);
if (name) {
camel_index_write_name (summary->priv->index, name);
g_object_unref (name);
camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (summary->priv->filter_index), NULL);
}
g_rec_mutex_unlock (&summary->priv->filter_lock);
return info;
}
/**
* camel_folder_summary_touch:
* @summary: a #CamelFolderSummary object
*
* Mark the summary as changed, so that a save will force it to be
* written back to disk.
**/
void
camel_folder_summary_touch (CamelFolderSummary *summary)
{
camel_folder_summary_lock (summary);
summary->priv->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_clear:
* @summary: a #CamelFolderSummary object
* @error: return location for a #GError, or %NULL
*
* Empty the summary contents.
*
* Returns: whether succeeded
**/
gboolean
camel_folder_summary_clear (CamelFolderSummary *summary,
GError **error)
{
GObject *summary_object;
CamelStore *parent_store;
CamelDB *cdb;
const gchar *folder_name;
gboolean res;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
camel_folder_summary_lock (summary);
if (camel_folder_summary_count (summary) == 0) {
camel_folder_summary_unlock (summary);
return TRUE;
}
g_hash_table_remove_all (summary->priv->uids);
remove_all_loaded (summary);
g_hash_table_remove_all (summary->priv->loaded_infos);
summary->priv->saved_count = 0;
summary->priv->unread_count = 0;
summary->priv->deleted_count = 0;
summary->priv->junk_count = 0;
summary->priv->junk_not_deleted_count = 0;
summary->priv->visible_count = 0;
camel_folder_summary_touch (summary);
folder_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store) {
camel_folder_summary_unlock (summary);
return FALSE;
}
cdb = camel_store_get_db (parent_store);
if (!is_in_memory_summary (summary))
res = camel_db_clear_folder_summary (cdb, folder_name, error) == 0;
else
res = TRUE;
summary_object = G_OBJECT (summary);
g_object_freeze_notify (summary_object);
g_object_notify (summary_object, "saved-count");
g_object_notify (summary_object, "unread-count");
g_object_notify (summary_object, "deleted-count");
g_object_notify (summary_object, "junk-count");
g_object_notify (summary_object, "junk-not-deleted-count");
g_object_notify (summary_object, "visible-count");
g_object_thaw_notify (summary_object);
camel_folder_summary_unlock (summary);
return res;
}
/**
* camel_folder_summary_remove:
* @summary: a #CamelFolderSummary object
* @info: a #CamelMessageInfo
*
* Remove a specific @info record from the summary.
*
* Returns: Whether the @info was found and removed from the @summary.
**/
gboolean
camel_folder_summary_remove (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
g_return_val_if_fail (info != NULL, FALSE);
return camel_folder_summary_remove_uid (summary, camel_message_info_get_uid (info));
}
/**
* camel_folder_summary_remove_uid:
* @summary: a #CamelFolderSummary object
* @uid: a uid
*
* Remove a specific info record from the summary, by @uid.
*
* Returns: Whether the @uid was found and removed from the @summary.
**/
gboolean
camel_folder_summary_remove_uid (CamelFolderSummary *summary,
const gchar *uid)
{
gpointer ptr_uid = NULL, ptr_flags = NULL;
CamelMessageInfo *mi;
CamelStore *parent_store;
const gchar *full_name;
const gchar *uid_copy;
gboolean res = TRUE;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
g_return_val_if_fail (uid != NULL, FALSE);
camel_folder_summary_lock (summary);
if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
camel_folder_summary_unlock (summary);
return FALSE;
}
folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
uid_copy = camel_pstring_strdup (uid);
g_hash_table_remove (summary->priv->uids, uid_copy);
mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
if (mi) {
/* Dirty hack, to have CamelWeakRefGroup properly cleared,
when the message info leaks due to ref/unref imbalance. */
_camel_message_info_unset_summary (mi);
g_clear_object (&mi);
}
if (!is_in_memory_summary (summary)) {
full_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store || camel_db_delete_uid (camel_store_get_db (parent_store), full_name, uid_copy, NULL) != 0)
res = FALSE;
}
camel_pstring_free (uid_copy);
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
return res;
}
/**
* camel_folder_summary_remove_uids:
* @summary: a #CamelFolderSummary object
* @uids: (element-type utf8): a GList of uids
*
* Remove a specific info record from the summary, by @uid.
*
* Returns: Whether the @uid was found and removed from the @summary.
*
* Since: 3.6
**/
gboolean
camel_folder_summary_remove_uids (CamelFolderSummary *summary,
GList *uids)
{
CamelStore *parent_store;
const gchar *full_name;
GList *l;
gboolean res = TRUE;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
g_return_val_if_fail (uids != NULL, FALSE);
g_object_freeze_notify (G_OBJECT (summary));
camel_folder_summary_lock (summary);
for (l = g_list_first (uids); l; l = g_list_next (l)) {
gpointer ptr_uid = NULL, ptr_flags = NULL;
if (g_hash_table_lookup_extended (summary->priv->uids, l->data, &ptr_uid, &ptr_flags)) {
const gchar *uid_copy = camel_pstring_strdup (l->data);
CamelMessageInfo *mi;
folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
g_hash_table_remove (summary->priv->uids, uid_copy);
mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
if (mi) {
/* Dirty hack, to have CamelWeakRefGroup properly cleared,
when the message info leaks due to ref/unref imbalance. */
_camel_message_info_unset_summary (mi);
g_clear_object (&mi);
}
camel_pstring_free (uid_copy);
}
}
if (!is_in_memory_summary (summary)) {
full_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
if (!parent_store || camel_db_delete_uids (camel_store_get_db (parent_store), full_name, uids, NULL) != 0)
res = FALSE;
}
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
g_object_thaw_notify (G_OBJECT (summary));
return res;
}
/* are these even useful for anything??? */
static CamelMessageInfo *
message_info_new_from_parser (CamelFolderSummary *summary,
CamelMimeParser *mp)
{
CamelFolderSummaryClass *klass;
CamelMessageInfo *mi = NULL;
CamelNameValueArray *headers;
gint state;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
state = camel_mime_parser_state (mp);
switch (state) {
case CAMEL_MIME_PARSER_STATE_HEADER:
case CAMEL_MIME_PARSER_STATE_MESSAGE:
case CAMEL_MIME_PARSER_STATE_MULTIPART:
headers = camel_mime_parser_dup_headers (mp);
mi = klass->message_info_new_from_headers (summary, headers);
camel_name_value_array_free (headers);
break;
default:
g_error ("Invalid parser state");
}
return mi;
}
static CamelMessageInfo *
message_info_new_from_message (CamelFolderSummary *summary,
CamelMimeMessage *msg)
{
CamelFolderSummaryClass *klass;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
return klass->message_info_new_from_headers (summary, camel_medium_get_headers (CAMEL_MEDIUM (msg)));
}
static gchar *
summary_format_address (const CamelNameValueArray *headers,
const gchar *name,
const gchar *charset)
{
CamelHeaderAddress *addr = NULL;
gchar *text = NULL, *str = NULL;
const gchar *value;
value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, name);
if (!value)
return NULL;
while (*value && g_ascii_isspace (*value))
value++;
text = camel_header_unfold (value);
if ((addr = camel_header_address_decode (text, charset))) {
str = camel_header_address_list_format (addr);
camel_header_address_list_clear (&addr);
/* Special-case empty email part only here, not in the camel_header_address_list_format(),
to cover only the user-visible string, which looks odd with the empty email address. */
if (str && g_str_has_suffix (str, " <>") && strlen (str) > 3) {
str[strlen (str) - 3] = '\0';
}
g_free (text);
} else {
str = text;
}
return str;
}
static gchar *
summary_format_string (const CamelNameValueArray *headers,
const gchar *name,
const gchar *charset)
{
gchar *text, *str;
const gchar *value;
value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, name);
if (!value)
return NULL;
while (*value && g_ascii_isspace (*value))
value++;
text = camel_header_unfold (value);
str = camel_header_decode_string (text, charset);
g_free (text);
return str;
}
static CamelMessageInfo *
message_info_new_from_headers (CamelFolderSummary *summary,
const CamelNameValueArray *headers)
{
const gchar *received, *date, *content, *charset = NULL, *msgid;
GSList *refs, *irt, *scan;
gchar *subject, *from, *to, *cc, *mlist;
CamelContentType *ct = NULL;
CamelMessageInfo *mi;
guint count;
mi = camel_message_info_new (summary);
camel_message_info_set_abort_notifications (mi, TRUE);
if ((content = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Content-Type"))
&& (ct = camel_content_type_decode (content))
&& (charset = camel_content_type_param (ct, "charset"))
&& (g_ascii_strcasecmp (charset, "us-ascii") == 0))
charset = NULL;
charset = charset ? camel_iconv_charset_name (charset) : NULL;
subject = summary_format_string (headers, "subject", charset);
from = summary_format_address (headers, "from", charset);
to = summary_format_address (headers, "to", charset);
cc = summary_format_address (headers, "cc", charset);
mlist = camel_headers_dup_mailing_list (headers);
camel_message_info_set_subject (mi, subject);
camel_message_info_set_from (mi, from);
camel_message_info_set_to (mi, to);
camel_message_info_set_cc (mi, cc);
camel_message_info_set_mlist (mi, mlist);
g_free (subject);
g_free (from);
g_free (to);
g_free (cc);
g_free (mlist);
camel_util_fill_message_info_user_headers (mi, headers);
if ((date = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Date")))
camel_message_info_set_date_sent (mi, camel_header_decode_date (date, NULL));
else
camel_message_info_set_date_sent (mi, 0);
received = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Received");
if (received)
received = strrchr (received, ';');
if (received)
camel_message_info_set_date_received (mi, camel_header_decode_date (received + 1, NULL));
else
camel_message_info_set_date_received (mi, 0);
/* Fallback to Received date, when the Date header is missing */
if (!camel_message_info_get_date_sent (mi))
camel_message_info_set_date_sent (mi, camel_message_info_get_date_received (mi));
/* If neither Received is available, then use the current time. */
if (!camel_message_info_get_date_sent (mi))
camel_message_info_set_date_sent (mi, (gint64) time (NULL));
msgid = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Message-ID");
if (msgid)
camel_message_info_set_message_id (mi, camel_folder_search_util_hash_message_id (msgid, TRUE));
/* decode our references and in-reply-to headers */
refs = camel_header_references_decode (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "References"));
irt = camel_header_references_decode (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "In-Reply-To"));
if (refs || irt) {
GArray *references;
if (irt) {
/* The References field is populated from the "References" and/or "In-Reply-To"
* headers. If both headers exist, take the first thing in the In-Reply-To header
* that looks like a Message-ID, and append it to the References header. */
if (refs)
irt->next = refs;
refs = irt;
}
count = g_slist_length (refs);
references = g_array_sized_new (FALSE, FALSE, sizeof (guint64), count);
for (scan = refs; scan != NULL; scan = g_slist_next (scan)) {
guint64 msgid_hash;
msgid_hash = camel_folder_search_util_hash_message_id (scan->data, FALSE);
g_array_append_val (references, msgid_hash);
}
g_slist_free_full (refs, g_free);
camel_message_info_take_references (mi, references);
}
content = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Content-class");
if ((content && g_ascii_strcasecmp (content, "urn:content-classes:calendarmessage") == 0) ||
(ct && camel_content_type_is (ct, "text", "calendar")) ||
camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Calendar-Attachment"))
camel_message_info_set_user_flag (mi, "$has_cal", TRUE);
if (camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Evolution-Note"))
camel_message_info_set_user_flag (mi, "$has_note", TRUE);
if (ct)
camel_content_type_unref (ct);
/* Headers are meant to be used when filtering, to speed things up.
Do not save them, when the folder is not expected to be filtered. */
if (summary && summary->priv->folder &&
(camel_folder_get_flags (summary->priv->folder) & (CAMEL_FOLDER_FILTER_RECENT | CAMEL_FOLDER_FILTER_JUNK)) != 0)
camel_message_info_take_headers (mi, camel_name_value_array_copy (headers));
camel_message_info_set_abort_notifications (mi, FALSE);
return mi;
}
static gchar *
next_uid_string (CamelFolderSummary *summary)
{
return g_strdup_printf ("%u", camel_folder_summary_next_uid (summary));
}
/*
OK
Now this is where all the "smarts" happen, where the content info is built,
and any indexing and what not is performed
*/
/* must have filter_lock before calling this function */
static void
summary_traverse_content_with_parser (CamelFolderSummary *summary,
CamelMessageInfo *msginfo,
CamelMimeParser *mp)
{
gint state;
gsize len;
gchar *buffer;
CamelContentType *ct;
gint enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
CamelMimeFilter *mfc;
const gchar *calendar_header;
d (printf ("traversing content\n"));
/* start of this part */
state = camel_mime_parser_step (mp, &buffer, &len);
switch (state) {
case CAMEL_MIME_PARSER_STATE_HEADER:
/* check content type for indexing, then read body */
ct = camel_mime_parser_content_type (mp);
/* update attachments flag as we go */
if (camel_content_type_is (ct, "application", "pgp-signature")
#ifdef ENABLE_SMIME
|| camel_content_type_is (ct, "application", "pkcs7-signature")
|| camel_content_type_is (ct, "application", "xpkcs7signature")
|| camel_content_type_is (ct, "application", "xpkcs7-signature")
|| camel_content_type_is (ct, "application", "x-pkcs7-signature")
#endif
)
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
calendar_header = camel_mime_parser_header (mp, "Content-class", NULL);
if (calendar_header && g_ascii_strcasecmp (calendar_header, "urn:content-classes:calendarmessage") != 0)
calendar_header = NULL;
if (!calendar_header)
calendar_header = camel_mime_parser_header (mp, "X-Calendar-Attachment", NULL);
if (calendar_header || camel_content_type_is (ct, "text", "calendar"))
camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
if (camel_mime_parser_header (mp, "X-Evolution-Note", NULL))
camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
if (summary->priv->index && camel_content_type_is (ct, "text", "*")) {
gchar *encoding;
const gchar *charset;
d (printf ("generating index:\n"));
encoding = camel_content_transfer_encoding_decode (camel_mime_parser_header (mp, "content-transfer-encoding", NULL));
if (encoding) {
if (!g_ascii_strcasecmp (encoding, "base64")) {
d (printf (" decoding base64\n"));
if (summary->priv->filter_64 == NULL)
summary->priv->filter_64 = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
else
camel_mime_filter_reset (summary->priv->filter_64);
enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_64);
} else if (!g_ascii_strcasecmp (encoding, "quoted-printable")) {
d (printf (" decoding quoted-printable\n"));
if (summary->priv->filter_qp == NULL)
summary->priv->filter_qp = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_DEC);
else
camel_mime_filter_reset (summary->priv->filter_qp);
enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_qp);
} else if (!g_ascii_strcasecmp (encoding, "x-uuencode") ||
!g_ascii_strcasecmp (encoding, "uuencode")) {
d (printf (" decoding x-uuencode\n"));
if (summary->priv->filter_uu == NULL)
summary->priv->filter_uu = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
else
camel_mime_filter_reset (summary->priv->filter_uu);
enc_id = camel_mime_parser_filter_add (mp, summary->priv->filter_uu);
} else {
d (printf (" ignoring encoding %s\n", encoding));
}
g_free (encoding);
}
charset = camel_content_type_param (ct, "charset");
if (charset != NULL
&& !(g_ascii_strcasecmp (charset, "us-ascii") == 0
|| g_ascii_strcasecmp (charset, "utf-8") == 0)) {
d (printf (" Adding conversion filter from %s to UTF-8\n", charset));
mfc = g_hash_table_lookup (summary->priv->filter_charset, charset);
if (mfc == NULL) {
mfc = camel_mime_filter_charset_new (charset, "UTF-8");
if (mfc)
g_hash_table_insert (summary->priv->filter_charset, g_strdup (charset), mfc);
} else {
camel_mime_filter_reset ((CamelMimeFilter *) mfc);
}
if (mfc) {
chr_id = camel_mime_parser_filter_add (mp, mfc);
} else {
w (g_warning ("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset));
}
}
/* we do charset conversions before this filter, which isn't strictly correct,
* but works in most cases */
if (camel_content_type_is (ct, "text", "html")) {
if (summary->priv->filter_html == NULL)
summary->priv->filter_html = camel_mime_filter_html_new ();
else
camel_mime_filter_reset ((CamelMimeFilter *) summary->priv->filter_html);
html_id = camel_mime_parser_filter_add (mp, (CamelMimeFilter *) summary->priv->filter_html);
}
/* and this filter actually does the indexing */
idx_id = camel_mime_parser_filter_add (mp, summary->priv->filter_index);
}
/* and scan/index everything */
while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
;
/* and remove the filters */
camel_mime_parser_filter_remove (mp, enc_id);
camel_mime_parser_filter_remove (mp, chr_id);
camel_mime_parser_filter_remove (mp, html_id);
camel_mime_parser_filter_remove (mp, idx_id);
break;
case CAMEL_MIME_PARSER_STATE_MULTIPART:
d (printf ("Summarising multipart\n"));
/* update attachments flag as we go */
ct = camel_mime_parser_content_type (mp);
if (camel_content_type_is (ct, "multipart", "mixed"))
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
if (camel_content_type_is (ct, "multipart", "signed")
|| camel_content_type_is (ct, "multipart", "encrypted"))
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
while (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
camel_mime_parser_unstep (mp);
summary_traverse_content_with_parser (summary, msginfo, mp);
}
break;
case CAMEL_MIME_PARSER_STATE_MESSAGE:
d (printf ("Summarising message\n"));
/* update attachments flag as we go */
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
summary_traverse_content_with_parser (summary, msginfo, mp);
state = camel_mime_parser_step (mp, &buffer, &len);
if (state != CAMEL_MIME_PARSER_STATE_MESSAGE_END) {
g_error ("Bad parser state: Expecing MESSAGE_END or MESSAGE_EOF, got: %d", state);
camel_mime_parser_unstep (mp);
}
break;
}
d (printf ("finished traversion content info\n"));
}
/* build the content-info, from a message */
/* this needs the filter lock since it uses filters to perform indexing */
static void
summary_traverse_content_with_part (CamelFolderSummary *summary,
CamelMessageInfo *msginfo,
CamelMimePart *object)
{
CamelDataWrapper *containee;
gint parts, i;
CamelContentType *ct;
const CamelNameValueArray *headers;
gboolean is_calendar = FALSE, is_note = FALSE;
const gchar *header_name, *header_value;
containee = camel_medium_get_content (CAMEL_MEDIUM (object));
if (containee == NULL)
return;
/* TODO: I find it odd that get_part and get_content do not
* add a reference, probably need fixing for multithreading */
/* check for attachments */
ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (containee));
if (camel_content_type_is (ct, "multipart", "*")) {
if (camel_content_type_is (ct, "multipart", "mixed"))
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
if (camel_content_type_is (ct, "multipart", "signed")
|| camel_content_type_is (ct, "multipart", "encrypted"))
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
} else if (camel_content_type_is (ct, "application", "pgp-signature")
#ifdef ENABLE_SMIME
|| camel_content_type_is (ct, "application", "pkcs7-signature")
|| camel_content_type_is (ct, "application", "xpkcs7signature")
|| camel_content_type_is (ct, "application", "xpkcs7-signature")
|| camel_content_type_is (ct, "application", "x-pkcs7-signature")
#endif
) {
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
}
headers = camel_medium_get_headers (CAMEL_MEDIUM (object));
for (i = 0; camel_name_value_array_get (headers, i, &header_name, &header_value); i++) {
const gchar *value = header_value;
while (value && *value && g_ascii_isspace (*value))
value++;
if (header_name && value && (
(g_ascii_strcasecmp (header_name, "Content-class") == 0 && g_ascii_strcasecmp (value, "urn:content-classes:calendarmessage") == 0) ||
(g_ascii_strcasecmp (header_name, "X-Calendar-Attachment") == 0))) {
is_calendar = TRUE;
if (is_note)
break;
}
if (header_name && value && g_ascii_strcasecmp (header_name, "X-Evolution-Note") == 0) {
is_note = TRUE;
if (is_calendar)
break;
}
}
if (is_calendar || camel_content_type_is (ct, "text", "calendar"))
camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
if (is_note)
camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
/* using the object types is more accurate than using the mime/types */
if (CAMEL_IS_MULTIPART (containee)) {
parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
for (i = 0; i < parts; i++) {
CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
g_return_if_fail (part);
summary_traverse_content_with_part (summary, msginfo, part);
}
} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
/* for messages we only look at its contents */
summary_traverse_content_with_part (summary, msginfo, (CamelMimePart *) containee);
} else if (summary->priv->filter_stream
&& camel_content_type_is (ct, "text", "*")) {
gint html_id = -1, idx_id = -1;
/* pre-attach html filter if required, otherwise just index filter */
if (camel_content_type_is (ct, "text", "html")) {
if (summary->priv->filter_html == NULL)
summary->priv->filter_html = camel_mime_filter_html_new ();
else
camel_mime_filter_reset ((CamelMimeFilter *) summary->priv->filter_html);
html_id = camel_stream_filter_add (
CAMEL_STREAM_FILTER (summary->priv->filter_stream),
(CamelMimeFilter *) summary->priv->filter_html);
}
idx_id = camel_stream_filter_add (
CAMEL_STREAM_FILTER (summary->priv->filter_stream),
summary->priv->filter_index);
/* FIXME Pass a GCancellable and GError here. */
camel_data_wrapper_decode_to_stream_sync (
containee, summary->priv->filter_stream, NULL, NULL);
camel_stream_flush (summary->priv->filter_stream, NULL, NULL);
camel_stream_filter_remove (
CAMEL_STREAM_FILTER (summary->priv->filter_stream), idx_id);
camel_stream_filter_remove (
CAMEL_STREAM_FILTER (summary->priv->filter_stream), html_id);
}
}
static struct flag_names_t {
const gchar *name;
guint32 value;
} flag_names[] = {
{ "answered", CAMEL_MESSAGE_ANSWERED },
{ "deleted", CAMEL_MESSAGE_DELETED },
{ "draft", CAMEL_MESSAGE_DRAFT },
{ "flagged", CAMEL_MESSAGE_FLAGGED },
{ "seen", CAMEL_MESSAGE_SEEN },
{ "attachments", CAMEL_MESSAGE_ATTACHMENTS },
{ "junk", CAMEL_MESSAGE_JUNK },
{ "notjunk", CAMEL_MESSAGE_NOTJUNK },
{ "secure", CAMEL_MESSAGE_SECURE },
{ "junklearn", CAMEL_MESSAGE_JUNK_LEARN },
{ NULL, 0 }
};
/**
* camel_system_flag:
* @name: name of a system flag
*
* Returns: the integer value of the system flag string
**/
CamelMessageFlags
camel_system_flag (const gchar *name)
{
struct flag_names_t *flag;
g_return_val_if_fail (name != NULL, 0);
for (flag = flag_names; flag->name; flag++)
if (!g_ascii_strcasecmp (name, flag->name))
return flag->value;
return 0;
}
/**
* camel_system_flag_get:
* @flags: bitwise system flags
* @name: name of the flag to check for
*
* Find the state of the flag @name in @flags.
*
* Returns: %TRUE if the named flag is set or %FALSE otherwise
**/
gboolean
camel_system_flag_get (CamelMessageFlags flags,
const gchar *name)
{
g_return_val_if_fail (name != NULL, FALSE);
return flags & camel_system_flag (name);
}
/**
* camel_message_info_new_from_headers:
* @summary: (nullable): a #CamelFolderSummary object or %NULL
* @headers: a #CamelNameValueArray
*
* Create a new #CamelMessageInfo pre-populated with info from
* @headers.
*
* Returns: (transfer full): a new #CamelMessageInfo
*
* Since: 3.24
**/
CamelMessageInfo *
camel_message_info_new_from_headers (CamelFolderSummary *summary,
const CamelNameValueArray *headers)
{
if (summary != NULL) {
CamelFolderSummaryClass *klass;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (klass->message_info_new_from_headers != NULL, NULL);
return klass->message_info_new_from_headers (summary, headers);
} else {
return message_info_new_from_headers (NULL, headers);
}
}
/**
* camel_folder_summary_lock:
* @summary: a #CamelFolderSummary
*
* Locks @summary. Unlock it with camel_folder_summary_unlock().
*
* Since: 2.32
**/
void
camel_folder_summary_lock (CamelFolderSummary *summary)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
g_rec_mutex_lock (&summary->priv->summary_lock);
}
/**
* camel_folder_summary_unlock:
* @summary: a #CamelFolderSummary
*
* Unlocks @summary, previously locked with camel_folder_summary_lock().
*
* Since: 2.32
**/
void
camel_folder_summary_unlock (CamelFolderSummary *summary)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
g_rec_mutex_unlock (&summary->priv->summary_lock);
}