/* -*- 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
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#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-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-vee-folder.h"
#include "camel-vtrash-folder.h"
#include "camel-mime-part-utils.h"
#define CAMEL_FOLDER_SUMMARY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummaryPrivate))
/* Make 5 minutes as default cache drop */
#define SUMMARY_CACHE_DROP 300
#define dd(x) if (camel_debug("sync")) x
struct _CamelFolderSummaryPrivate {
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 */
gboolean need_preview;
GHashTable *preview_updates;
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;
gboolean build_content; /* do we try and parse/index the content, or not? */
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 struct _node *my_list_append (struct _node **list, struct _node *n);
static gint my_list_size (struct _node **list);
static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
static CamelMessageInfo * message_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
static CamelMessageInfo * message_info_new_from_message (CamelFolderSummary *summary, CamelMimeMessage *msg, const gchar *bodystructure);
static void message_info_free (CamelFolderSummary *, CamelMessageInfo *);
static CamelMessageContentInfo * content_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
static CamelMessageContentInfo * content_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
static CamelMessageContentInfo * content_info_new_from_message (CamelFolderSummary *summary, CamelMimePart *mp);
static void content_info_free (CamelFolderSummary *, CamelMessageContentInfo *);
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 CamelMessageContentInfo * summary_build_content_info (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimeParser *mp);
static CamelMessageContentInfo * summary_build_content_info_message (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimePart *object);
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,
PROP_BUILD_CONTENT,
PROP_NEED_PREVIEW
};
G_DEFINE_TYPE (CamelFolderSummary, camel_folder_summary, G_TYPE_OBJECT)
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;
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);
g_slist_foreach (to_remove_infos, (GFunc) camel_message_info_unref, NULL);
g_slist_free (to_remove_infos);
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)
{
CamelFolderSummaryPrivate *priv;
priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (object);
if (priv->timeout_handle) {
/* this should not happen, because the release timer
* holds a reference on object */
g_source_remove (priv->timeout_handle);
priv->timeout_handle = 0;
}
g_clear_object (&priv->filter_index);
g_clear_object (&priv->filter_64);
g_clear_object (&priv->filter_qp);
g_clear_object (&priv->filter_uu);
g_clear_object (&priv->filter_save);
g_clear_object (&priv->filter_html);
g_clear_object (&priv->filter_stream);
g_clear_object (&priv->filter_index);
if (priv->folder) {
g_object_weak_unref (G_OBJECT (priv->folder), (GWeakNotify) g_nullify_pointer, &priv->folder);
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);
CamelFolderSummaryPrivate *priv = summary->priv;
g_hash_table_destroy (priv->uids);
remove_all_loaded (summary);
g_hash_table_destroy (priv->loaded_infos);
g_hash_table_foreach (priv->filter_charset, free_o_name, NULL);
g_hash_table_destroy (priv->filter_charset);
g_hash_table_destroy (priv->preview_updates);
g_rec_mutex_clear (&priv->summary_lock);
g_rec_mutex_clear (&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;
case PROP_BUILD_CONTENT:
camel_folder_summary_set_build_content (
CAMEL_FOLDER_SUMMARY (object),
g_value_get_boolean (value));
return;
case PROP_NEED_PREVIEW:
camel_folder_summary_set_need_preview (
CAMEL_FOLDER_SUMMARY (object),
g_value_get_boolean (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;
case PROP_BUILD_CONTENT:
g_value_set_boolean (
value,
camel_folder_summary_get_build_content (
CAMEL_FOLDER_SUMMARY (object)));
return;
case PROP_NEED_PREVIEW:
g_value_set_boolean (
value,
camel_folder_summary_get_need_preview (
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->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 && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
}
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) {
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_from_db (CamelFolderSummary *summary,
CamelFIRecord *record)
{
io (printf ("Loading header from db \n"));
summary->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->version > 0xff) && (summary->version & 0xff) < 12) {
io (printf ("Summary header version mismatch"));
errno = EINVAL;
return FALSE;
}
if (!(summary->version < 0x100 && summary->version >= 13))
io (printf ("Loading legacy summary\n"));
else
io (printf ("loading new-format summary\n"));
#endif
summary->flags = record->flags;
summary->priv->nextuid = record->nextuid;
summary->time = record->time;
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_to_db (CamelFolderSummary *summary,
GError **error)
{
CamelFIRecord * record = g_new0 (CamelFIRecord, 1);
CamelStore *parent_store;
CamelDB *db;
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);
db = parent_store->cdb_w;
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->flags;
record->nextuid = summary->priv->nextuid;
record->time = summary->time;
if (!is_in_memory_summary (summary)) {
/* FIXME: Ever heard of Constructors and initializing ? */
if (camel_db_count_total_message_info (db, table_name, &(record->saved_count), NULL))
record->saved_count = 0;
if (camel_db_count_junk_message_info (db, table_name, &(record->junk_count), NULL))
record->junk_count = 0;
if (camel_db_count_deleted_message_info (db, table_name, &(record->deleted_count), NULL))
record->deleted_count = 0;
if (camel_db_count_unread_message_info (db, table_name, &(record->unread_count), NULL))
record->unread_count = 0;
if (camel_db_count_visible_message_info (db, table_name, &(record->visible_count), NULL))
record->visible_count = 0;
if (camel_db_count_junk_not_deleted_message_info (db, 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;
}
static CamelMessageInfo *
message_info_from_db (CamelFolderSummary *summary,
CamelMIRecord *record)
{
CamelMessageInfoBase *mi;
gint i;
gint count;
gchar *part, *label;
mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
io (printf ("Loading message info from db\n"));
mi->flags = record->flags;
mi->size = record->size;
mi->date_sent = record->dsent;
mi->date_received = record->dreceived;
mi->uid = (gchar *) camel_pstring_strdup (record->uid);
mi->subject = (gchar *) camel_pstring_add (record->subject, FALSE);
mi->from = (gchar *) camel_pstring_add (record->from, FALSE);
mi->to = (gchar *) camel_pstring_add (record->to, FALSE);
mi->cc = (gchar *) camel_pstring_add (record->cc, FALSE);
mi->mlist = (gchar *) camel_pstring_add (record->mlist, FALSE);
/* Evolution itself doesn't yet use this, so we ignore it (saving some memory) */
mi->bodystructure = NULL;
/* Extract Message id & References */
mi->content = NULL;
part = record->part;
if (part) {
mi->message_id.id.part.hi = bdata_extract_digit (&part);
mi->message_id.id.part.lo = bdata_extract_digit (&part);
count = bdata_extract_digit (&part);
if (count > 0) {
mi->references = g_malloc (sizeof (*mi->references) + ((count - 1) * sizeof (mi->references->references[0])));
mi->references->size = count;
for (i = 0; i < count; i++) {
mi->references->references[i].id.part.hi = bdata_extract_digit (&part);
mi->references->references[i].id.part.lo = bdata_extract_digit (&part);
}
} else
mi->references = NULL;
}
/* Extract User flags/labels */
part = record->labels;
if (part) {
label = part;
for (i = 0; part[i]; i++) {
if (part[i] == ' ') {
part[i] = 0;
camel_flag_set (&mi->user_flags, label, TRUE);
label = &(part[i + 1]);
}
}
camel_flag_set (&mi->user_flags, label, TRUE);
}
/* Extract User tags */
part = record->usertags;
count = bdata_extract_digit (&part);
for (i = 0; i < count; i++) {
gchar *name, *value;
name = bdata_extract_string (&part);
value = bdata_extract_string (&part);
camel_tag_set (&mi->user_tags, name, value);
g_free (name);
g_free (value);
}
return (CamelMessageInfo *) mi;
}
static CamelMIRecord *
message_info_to_db (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
CamelMIRecord *record = g_new0 (CamelMIRecord, 1);
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
GString *tmp;
CamelFlag *flag;
CamelTag *tag;
gint count, i;
/* Assume that we dont have to take care of DB Safeness. It will be done while doing the DB transaction */
record->uid = (gchar *) camel_pstring_strdup (camel_message_info_uid (mi));
record->flags = mi->flags;
record->read = ((mi->flags & (CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_JUNK))) ? 1 : 0;
record->deleted = mi->flags & CAMEL_MESSAGE_DELETED ? 1 : 0;
record->replied = mi->flags & CAMEL_MESSAGE_ANSWERED ? 1 : 0;
record->important = mi->flags & CAMEL_MESSAGE_FLAGGED ? 1 : 0;
record->junk = mi->flags & CAMEL_MESSAGE_JUNK ? 1 : 0;
record->dirty = mi->flags & CAMEL_MESSAGE_FOLDER_FLAGGED ? 1 : 0;
record->attachment = mi->flags & CAMEL_MESSAGE_ATTACHMENTS ? 1 : 0;
record->size = mi->size;
record->dsent = mi->date_sent;
record->dreceived = mi->date_received;
record->subject = (gchar *) camel_pstring_strdup (camel_message_info_subject (mi));
record->from = (gchar *) camel_pstring_strdup (camel_message_info_from (mi));
record->to = (gchar *) camel_pstring_strdup (camel_message_info_to (mi));
record->cc = (gchar *) camel_pstring_strdup (camel_message_info_cc (mi));
record->mlist = (gchar *) camel_pstring_strdup (camel_message_info_mlist (mi));
record->followup_flag = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "follow-up"));
record->followup_completed_on = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "completed-on"));
record->followup_due_by = (gchar *) camel_pstring_strdup (camel_message_info_user_tag (info, "due-by"));
record->bodystructure = mi->bodystructure ? g_strdup (mi->bodystructure) : NULL;
tmp = g_string_new (NULL);
if (mi->references) {
g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) mi->references->size);
for (i = 0; i < mi->references->size; i++)
g_string_append_printf (tmp, " %lu %lu", (gulong) mi->references->references[i].id.part.hi, (gulong) mi->references->references[i].id.part.lo);
} else {
g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) 0);
}
record->part = tmp->str;
g_string_free (tmp, FALSE);
tmp = g_string_new (NULL);
flag = mi->user_flags;
while (flag) {
g_string_append_printf (tmp, "%s ", flag->name);
flag = flag->next;
}
/* Strip off the last space */
if (tmp->len)
tmp->len--;
record->labels = tmp->str;
g_string_free (tmp, FALSE);
tmp = g_string_new (NULL);
count = camel_tag_list_size (&mi->user_tags);
g_string_append_printf (tmp, "%lu", (gulong) count);
tag = mi->user_tags;
while (tag) {
/* FIXME: Should we handle empty tags? Can it be empty? If it potential crasher ahead*/
g_string_append_printf (tmp, " %lu-%s %lu-%s", (gulong) strlen (tag->name), tag->name, (gulong) strlen (tag->value), tag->value);
tag = tag->next;
}
record->usertags = tmp->str;
g_string_free (tmp, FALSE);
return record;
}
static CamelMessageContentInfo *
content_info_from_db (CamelFolderSummary *summary,
CamelMIRecord *record)
{
CamelMessageContentInfo *ci;
gchar *type, *subtype;
guint32 count, i;
CamelContentType *ct;
gchar *part = record->cinfo;
io (printf ("Loading content info from db\n"));
if (!part)
return NULL;
ci = camel_folder_summary_content_info_new (summary);
if (*part == ' ') part++; /* Move off the space in the record */
type = bdata_extract_string (&part);
subtype = bdata_extract_string (&part);
ct = camel_content_type_new (type, subtype);
g_free (type); /* can this be removed? */
g_free (subtype);
count = bdata_extract_digit (&part);
for (i = 0; i < count; i++) {
gchar *name, *value;
name = bdata_extract_string (&part);
value = bdata_extract_string (&part);
camel_content_type_set_param (ct, name, value);
/* TODO: do this so we dont have to double alloc/free */
g_free (name);
g_free (value);
}
ci->type = ct;
/* FIXME[disk-summary] move all these to camel pstring */
ci->id = bdata_extract_string (&part);
ci->description = bdata_extract_string (&part);
ci->encoding = bdata_extract_string (&part);
ci->size = bdata_extract_digit (&part);
record->cinfo = part; /* Keep moving the cursor in the record */
ci->childs = NULL;
return ci;
}
static gboolean
content_info_to_db (CamelFolderSummary *summary,
CamelMessageContentInfo *ci,
CamelMIRecord *record)
{
CamelContentType *ct;
struct _camel_header_param *hp;
GString *str = g_string_new (NULL);
gchar *oldr;
io (printf ("Saving content info to db\n"));
ct = ci->type;
if (ct) {
if (ct->type)
g_string_append_printf (str, " %d-%s", (gint) strlen (ct->type), ct->type);
else
g_string_append_printf (str, " 0-");
if (ct->subtype)
g_string_append_printf (str, " %d-%s", (gint) strlen (ct->subtype), ct->subtype);
else
g_string_append_printf (str, " 0-");
g_string_append_printf (str, " %d", my_list_size ((struct _node **) &ct->params));
hp = ct->params;
while (hp) {
if (hp->name)
g_string_append_printf (str, " %d-%s", (gint) strlen (hp->name), hp->name);
else
g_string_append_printf (str, " 0-");
if (hp->value)
g_string_append_printf (str, " %d-%s", (gint) strlen (hp->value), hp->value);
else
g_string_append_printf (str, " 0-");
hp = hp->next;
}
} else {
g_string_append_printf (str, " %d-", 0);
g_string_append_printf (str, " %d-", 0);
g_string_append_printf (str, " %d", 0);
}
if (ci->id)
g_string_append_printf (str, " %d-%s", (gint) strlen (ci->id), ci->id);
else
g_string_append_printf (str, " 0-");
if (ci->description)
g_string_append_printf (str, " %d-%s", (gint) strlen (ci->description), ci->description);
else
g_string_append_printf (str, " 0-");
if (ci->encoding)
g_string_append_printf (str, " %d-%s", (gint) strlen (ci->encoding), ci->encoding);
else
g_string_append_printf (str, " 0-");
g_string_append_printf (str, " %u", ci->size);
if (record->cinfo) {
oldr = record->cinfo;
record->cinfo = g_strconcat (oldr, str->str, NULL);
g_free (oldr); g_string_free (str, TRUE);
} else {
record->cinfo = str->str;
g_string_free (str, FALSE);
}
return TRUE;
}
/**
* 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_uid (info) ||
!camel_folder_summary_check_uid (summary, camel_message_info_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_uid (info)));
new_flags = camel_message_info_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 && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
}
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_uid (info)),
GUINT_TO_POINTER (new_flags));
g_object_thaw_notify (summary_object);
camel_folder_summary_unlock (summary);
return changed;
}
static CamelMessageInfo *
message_info_clone (CamelFolderSummary *summary,
const CamelMessageInfo *mi)
{
CamelMessageInfoBase *to, *from = (CamelMessageInfoBase *) mi;
CamelFlag *flag;
CamelTag *tag;
to = (CamelMessageInfoBase *) camel_message_info_new (summary);
to->flags = from->flags;
to->size = from->size;
to->date_sent = from->date_sent;
to->date_received = from->date_received;
to->refcount = 1;
/* NB: We don't clone the uid */
to->subject = camel_pstring_strdup (from->subject);
to->from = camel_pstring_strdup (from->from);
to->to = camel_pstring_strdup (from->to);
to->cc = camel_pstring_strdup (from->cc);
to->mlist = camel_pstring_strdup (from->mlist);
memcpy (&to->message_id, &from->message_id, sizeof (to->message_id));
to->preview = g_strdup (from->preview);
if (from->references) {
gint len = sizeof (*from->references) + ((from->references->size - 1) * sizeof (from->references->references[0]));
to->references = g_malloc (len);
memcpy (to->references, from->references, len);
}
flag = from->user_flags;
while (flag) {
camel_flag_set (&to->user_flags, flag->name, TRUE);
flag = flag->next;
}
tag = from->user_tags;
while (tag) {
camel_tag_set (&to->user_tags, tag->name, tag->value);
tag = tag->next;
}
if (from->content) {
/* FIXME: copy content-infos */
}
return (CamelMessageInfo *) to;
}
static gconstpointer
info_ptr (const CamelMessageInfo *mi,
gint id)
{
switch (id) {
case CAMEL_MESSAGE_INFO_SUBJECT:
return ((const CamelMessageInfoBase *) mi)->subject;
case CAMEL_MESSAGE_INFO_FROM:
return ((const CamelMessageInfoBase *) mi)->from;
case CAMEL_MESSAGE_INFO_TO:
return ((const CamelMessageInfoBase *) mi)->to;
case CAMEL_MESSAGE_INFO_CC:
return ((const CamelMessageInfoBase *) mi)->cc;
case CAMEL_MESSAGE_INFO_MLIST:
return ((const CamelMessageInfoBase *) mi)->mlist;
case CAMEL_MESSAGE_INFO_MESSAGE_ID:
return &((const CamelMessageInfoBase *) mi)->message_id;
case CAMEL_MESSAGE_INFO_REFERENCES:
return ((const CamelMessageInfoBase *) mi)->references;
case CAMEL_MESSAGE_INFO_USER_FLAGS:
return ((const CamelMessageInfoBase *) mi)->user_flags;
case CAMEL_MESSAGE_INFO_USER_TAGS:
return ((const CamelMessageInfoBase *) mi)->user_tags;
case CAMEL_MESSAGE_INFO_HEADERS:
return ((const CamelMessageInfoBase *) mi)->headers;
case CAMEL_MESSAGE_INFO_CONTENT:
return ((const CamelMessageInfoBase *) mi)->content;
case CAMEL_MESSAGE_INFO_PREVIEW:
return ((const CamelMessageInfoBase *) mi)->preview;
default:
g_return_val_if_reached (NULL);
}
}
static guint32
info_uint32 (const CamelMessageInfo *mi,
gint id)
{
switch (id) {
case CAMEL_MESSAGE_INFO_FLAGS:
return ((const CamelMessageInfoBase *) mi)->flags;
case CAMEL_MESSAGE_INFO_SIZE:
return ((const CamelMessageInfoBase *) mi)->size;
default:
g_return_val_if_reached (0);
}
}
static time_t
info_time (const CamelMessageInfo *mi,
gint id)
{
switch (id) {
case CAMEL_MESSAGE_INFO_DATE_SENT:
return ((const CamelMessageInfoBase *) mi)->date_sent;
case CAMEL_MESSAGE_INFO_DATE_RECEIVED:
return ((const CamelMessageInfoBase *) mi)->date_received;
default:
g_return_val_if_reached (0);
}
}
static gboolean
info_user_flag (const CamelMessageInfo *mi,
const gchar *id)
{
return camel_flag_get (&((CamelMessageInfoBase *) mi)->user_flags, id);
}
static const gchar *
info_user_tag (const CamelMessageInfo *mi,
const gchar *id)
{
return camel_tag_get (&((CamelMessageInfoBase *) mi)->user_tags, id);
}
static gboolean
info_set_user_flag (CamelMessageInfo *info,
const gchar *name,
gboolean value)
{
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
gint res;
res = camel_flag_set (&mi->user_flags, name, value);
if (mi->summary && res && mi->summary->priv->folder && mi->uid
&& camel_folder_summary_check_uid (mi->summary, mi->uid)) {
CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
mi->dirty = TRUE;
camel_folder_summary_touch (mi->summary);
camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
camel_folder_changed (mi->summary->priv->folder, changes);
camel_folder_change_info_free (changes);
}
return res;
}
static gboolean
info_set_user_tag (CamelMessageInfo *info,
const gchar *name,
const gchar *value)
{
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
gint res;
res = camel_tag_set (&mi->user_tags, name, value);
if (mi->summary && res && mi->summary->priv->folder && mi->uid
&& camel_folder_summary_check_uid (mi->summary, mi->uid)) {
CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
mi->dirty = TRUE;
camel_folder_summary_touch (mi->summary);
camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
camel_folder_changed (mi->summary->priv->folder, changes);
camel_folder_change_info_free (changes);
}
return res;
}
static gboolean
info_set_flags (CamelMessageInfo *info,
guint32 flags,
guint32 set)
{
guint32 old;
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
gboolean counts_changed = FALSE;
old = camel_message_info_flags (info);
mi->flags = (old & ~flags) | (set & flags);
if (old != mi->flags) {
mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
mi->dirty = TRUE;
if (mi->summary)
camel_folder_summary_touch (mi->summary);
}
if (mi->summary) {
camel_folder_summary_lock (mi->summary);
g_object_freeze_notify (G_OBJECT (mi->summary));
counts_changed = camel_folder_summary_replace_flags (mi->summary, info);
}
if (!counts_changed && ((old & ~CAMEL_MESSAGE_SYSTEM_MASK) == (mi->flags & ~CAMEL_MESSAGE_SYSTEM_MASK)) && !((set & CAMEL_MESSAGE_JUNK_LEARN) && !(set & CAMEL_MESSAGE_JUNK))) {
if (mi->summary) {
g_object_thaw_notify (G_OBJECT (mi->summary));
camel_folder_summary_unlock (mi->summary);
}
return FALSE;
}
if (mi->summary) {
g_object_thaw_notify (G_OBJECT (mi->summary));
camel_folder_summary_unlock (mi->summary);
}
if (mi->summary && mi->summary->priv->folder && mi->uid) {
CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
camel_folder_changed (mi->summary->priv->folder, changes);
camel_folder_change_info_free (changes);
}
return TRUE;
}
static void
camel_folder_summary_class_init (CamelFolderSummaryClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelFolderSummaryPrivate));
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_size = sizeof (CamelMessageInfoBase);
class->content_info_size = sizeof (CamelMessageContentInfo);
class->summary_header_from_db = summary_header_from_db;
class->summary_header_to_db = summary_header_to_db;
class->message_info_from_db = message_info_from_db;
class->message_info_to_db = message_info_to_db;
class->content_info_from_db = content_info_from_db;
class->content_info_to_db = content_info_to_db;
class->message_info_new_from_header = message_info_new_from_header;
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_free = message_info_free;
class->message_info_clone = message_info_clone;
class->message_info_from_uid = message_info_from_uid;
class->content_info_new_from_header = content_info_new_from_header;
class->content_info_new_from_parser = content_info_new_from_parser;
class->content_info_new_from_message = content_info_new_from_message;
class->content_info_free = content_info_free;
class->next_uid_string = next_uid_string;
class->info_ptr = info_ptr;
class->info_uint32 = info_uint32;
class->info_time = info_time;
class->info_user_flag = info_user_flag;
class->info_user_tag = info_user_tag;
class->info_set_user_flag = info_set_user_flag;
class->info_set_user_tag = info_set_user_tag;
class->info_set_flags = info_set_flags;
/**
* 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:build-content
*
* Whether to build CamelMessageInfo.content.
**/
g_object_class_install_property (
object_class,
PROP_BUILD_CONTENT,
g_param_spec_boolean (
"build-content",
"Build content",
"Whether to build CamelMessageInfo.content",
FALSE,
G_PARAM_READWRITE));
/**
* CamelFolderSummary:need-preview
*
**/
g_object_class_install_property (
object_class,
PROP_NEED_PREVIEW,
g_param_spec_boolean (
"need-preview",
"Need preview",
"",
FALSE,
G_PARAM_READWRITE));
}
static void
camel_folder_summary_init (CamelFolderSummary *summary)
{
summary->priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (summary);
summary->version = CAMEL_FOLDER_SUMMARY_VERSION;
summary->flags = 0;
summary->time = 0;
summary->priv->filter_charset = g_hash_table_new (
camel_strcase_hash, camel_strcase_equal);
summary->priv->need_preview = FALSE;
summary->priv->preview_updates = g_hash_table_new (g_str_hash, g_str_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: 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_index:
* @summary: a #CamelFolderSummary object
*
* Returns: 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_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: 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.
*
* Unlike earlier behaviour, build_content need not be set to perform indexing.
**/
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: 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_set_build_content:
* @summary: a #CamelFolderSummary object
* @state: to build or not to build the content
*
* Set a flag to tell the summary to build the content info summary
* (#CamelMessageInfo.content). The default is not to build content
* info summaries.
**/
void
camel_folder_summary_set_build_content (CamelFolderSummary *summary,
gboolean state)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (summary->priv->build_content == state)
return;
summary->priv->build_content = state;
g_object_notify (G_OBJECT (summary), "build-content");
}
/**
* camel_folder_summary_get_build_content:
* @summary: a #CamelFolderSummary object
*
* Returns: Whether to build #CamelMessageInfo.content.
*
* Since: 3.4
**/
gboolean
camel_folder_summary_get_build_content (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
return summary->priv->build_content;
}
/**
* camel_folder_summary_set_need_preview:
*
* Since: 2.28
**/
void
camel_folder_summary_set_need_preview (CamelFolderSummary *summary,
gboolean preview)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
summary->priv->need_preview = preview;
}
/**
* camel_folder_summary_get_need_preview:
*
* Since: 2.28
**/
gboolean
camel_folder_summary_get_need_preview (CamelFolderSummary *summary)
{
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
return summary->priv->need_preview;
}
/**
* 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->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_lookup_extended (summary->priv->uids, uid, NULL, NULL);
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: 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);
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: 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.
*
* 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:
*
* 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);
info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
if (info)
camel_message_info_ref (info);
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);
cdb = parent_store->cdb_r;
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)
camel_message_info_ref (info);
camel_folder_summary_unlock (summary);
return info;
}
/**
* camel_folder_summary_get:
* @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: 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->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 CamelMessageContentInfo *
perform_content_info_load_from_db (CamelFolderSummary *summary,
CamelMIRecord *mir)
{
gint i;
guint32 count;
CamelMessageContentInfo *ci, *pci;
gchar *part;
ci = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_from_db (summary, mir);
if (ci == NULL)
return NULL;
part = mir->cinfo;
if (!part)
return ci;
if (*part == ' ') part++;
count = bdata_extract_digit (&part);
mir->cinfo = part;
for (i = 0; i < count; i++) {
pci = perform_content_info_load_from_db (summary, mir);
if (pci ) {
my_list_append ((struct _node **) &ci->childs, (struct _node *) pci);
pci->parent = ci;
} else {
d (fprintf (stderr, "Summary file format messed up?"));
camel_folder_summary_content_info_free (summary, ci);
return NULL;
}
}
return ci;
}
static void
gather_dirty_or_flagged_uids (gpointer key,
gpointer value,
gpointer user_data)
{
const gchar *uid = key;
CamelMessageInfoBase *info = value;
GHashTable *hash = user_data;
if (info->dirty || (info->flags & 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:
*
* Since: 2.24
**/
GPtrArray *
camel_folder_summary_get_changed (CamelFolderSummary *summary)
{
GPtrArray *res;
GHashTable *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,
CamelMessageInfoBase *info,
gint *count)
{
if (info->dirty)
(*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,
CamelMessageInfoBase *info,
GSList **to_remove_infos)
{
if (info->refcount == 1 && !info->dirty && (info->flags & 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_SQLITE_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_foreach (to_remove_infos, (GFunc) camel_message_info_unref, NULL);
g_slist_free (to_remove_infos);
camel_folder_summary_unlock (summary);
summary->priv->cache_load_time = time (NULL);
}
static gboolean
cfs_try_release_memory (CamelFolderSummary *summary)
{
CamelStore *parent_store;
CamelSession *session;
gchar *description;
/* 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)
return TRUE;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
description = g_strdup_printf (_("Release unused memory for folder '%s'"), camel_folder_get_full_name (summary->priv->folder));
camel_session_submit_job (
session, description,
(CamelSessionCallback) remove_cache,
g_object_ref (summary),
(GDestroyNotify) 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) {
summary->priv->timeout_handle = g_timeout_add_seconds (
SUMMARY_CACHE_DROP,
(GSourceFunc) cfs_try_release_memory,
g_object_ref (summary));
g_source_set_name_by_id (
summary->priv->timeout_handle,
"[camel] cfs_try_release_memory");
}
}
/* 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);
}
/* Update preview of cached messages */
static void
msg_update_preview (const gchar *uid,
gpointer value,
CamelFolder *folder)
{
CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
CamelMimeMessage *msg;
CamelStore *parent_store;
const gchar *full_name;
full_name = camel_folder_get_full_name (folder);
parent_store = camel_folder_get_parent_store (folder);
/* FIXME Pass a GCancellable */
msg = camel_folder_get_message_sync (folder, uid, NULL, NULL);
if (msg != NULL) {
if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview) {
if (!is_in_memory_summary (folder->summary))
camel_db_write_preview_record (parent_store->cdb_w, full_name, info->uid, info->preview, NULL);
}
}
camel_message_info_unref (info);
}
static void
pick_uids (const gchar *uid,
CamelMessageInfoBase *mi,
GPtrArray *array)
{
if (mi->preview)
g_ptr_array_add (array, (gchar *) camel_pstring_strdup (uid));
}
static void
copy_all_uids_to_hash (gpointer uid,
gpointer hash)
{
g_return_if_fail (uid != NULL);
g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
}
static gboolean
fill_mi (const gchar *uid,
const gchar *msg,
CamelFolder *folder)
{
CamelMessageInfoBase *info;
info = g_hash_table_lookup (folder->summary->priv->loaded_infos, uid);
if (info) /* We re assign the memory of msg */
info->preview = (gchar *) msg;
camel_pstring_free (uid); /* unref the uid */
return TRUE;
}
static void
preview_update (CamelSession *session,
GCancellable *cancellable,
CamelFolder *folder,
GError **error)
{
/* FIXME: Either lock & use or copy & use.*/
GPtrArray *uids_uncached, *uids_array;
GHashTable *preview_data, *uids_hash;
CamelStore *parent_store;
const gchar *full_name;
gboolean is_in_memory = is_in_memory_summary (folder->summary);
gint i;
uids_array = camel_folder_summary_get_array (folder->summary);
uids_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
g_ptr_array_foreach (uids_array, copy_all_uids_to_hash, uids_hash);
uids_uncached = camel_folder_get_uncached_uids (folder, uids_array, NULL);
camel_folder_summary_free_array (uids_array);
uids_array = NULL;
full_name = camel_folder_get_full_name (folder);
parent_store = camel_folder_get_parent_store (folder);
preview_data = is_in_memory ? NULL : camel_db_get_folder_preview (parent_store->cdb_r, full_name, NULL);
if (preview_data) {
g_hash_table_foreach_remove (preview_data, (GHRFunc) fill_mi, folder);
g_hash_table_destroy (preview_data);
}
camel_folder_summary_lock (folder->summary);
g_hash_table_foreach (folder->summary->priv->loaded_infos, (GHFunc) pick_uids, uids_uncached);
camel_folder_summary_unlock (folder->summary);
for (i = 0; i < uids_uncached->len; i++) {
g_hash_table_remove (uids_hash, uids_uncached->pdata[i]);
}
camel_folder_lock (folder);
if (!is_in_memory)
camel_db_begin_transaction (parent_store->cdb_w, NULL);
g_hash_table_foreach (uids_hash, (GHFunc) msg_update_preview, folder);
if (!is_in_memory)
camel_db_end_transaction (parent_store->cdb_w, NULL);
camel_folder_unlock (folder);
camel_folder_free_uids (folder, uids_uncached);
g_hash_table_destroy (uids_hash);
}
/* end */
static gint
cfs_reload_from_db (CamelFolderSummary *summary,
GError **error)
{
CamelDB *cdb;
CamelStore *parent_store;
const gchar *folder_name;
gint ret = 0;
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 0;
folder_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
cdb = parent_store->cdb_r;
data.columns_hash = NULL;
data.summary = summary;
data.add = FALSE;
ret = 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);
/* FIXME Convert this to a GTask, submitted through
* camel_service_queue_task(). Then it won't
* have to call camel_folder_lock/unlock(). */
if (summary->priv->need_preview) {
CamelSession *session;
/* This may not be available in a case of this being called as part
of CamelSession's dispose, because the CamelService uses GWeakRef
object which is invalidates its content when it reaches the dispose. */
session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
if (session) {
gchar *description;
description = g_strdup_printf (_("Update preview data for folder '%s'"), camel_folder_get_full_name (summary->priv->folder));
camel_session_submit_job (
session, description,
(CamelSessionCallback) preview_update,
g_object_ref (summary->priv->folder),
(GDestroyNotify) g_object_unref);
g_object_unref (session);
g_free (description);
}
}
return ret == 0 ? 0 : -1;
}
/**
* camel_folder_summary_add_preview:
*
* Since: 2.28
**/
void
camel_folder_summary_add_preview (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
camel_folder_summary_lock (summary);
g_hash_table_insert (summary->priv->preview_updates, (gchar *) info->uid, ((CamelMessageInfoBase *) info)->preview);
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (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);
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_from_db:
*
* Since: 2.24
**/
gboolean
camel_folder_summary_load_from_db (CamelFolderSummary *summary,
GError **error)
{
CamelDB *cdb;
CamelStore *parent_store;
const gchar *full_name;
gint ret = 0;
GError *local_error = NULL;
g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
if (is_in_memory_summary (summary))
return TRUE;
camel_folder_summary_lock (summary);
camel_folder_summary_save_to_db (summary, NULL);
/* struct _db_pass_data data; */
d (printf ("\ncamel_folder_summary_load_from_db 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_from_db (summary, parent_store, full_name, error)) {
camel_folder_summary_unlock (summary);
return FALSE;
}
cdb = parent_store->cdb_r;
ret = camel_db_get_folder_uids (
cdb, full_name, summary->sort_by, summary->collate,
summary->priv->uids, &local_error);
if (local_error != NULL && local_error->message != NULL &&
strstr (local_error->message, "no such table") != NULL) {
g_clear_error (&local_error);
/* create table the first time it is accessed and missing */
ret = camel_db_prepare_message_info_table (cdb, full_name, error);
} else if (local_error != NULL)
g_propagate_error (error, local_error);
camel_folder_summary_unlock (summary);
return ret == 0;
}
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 = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_FLAGS:
mir->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_READ:
mir->read = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_DELETED:
mir->deleted = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_REPLIED:
mir->replied = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_IMPORTANT:
mir->important = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_JUNK:
mir->junk = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_ATTACHMENT:
mir->attachment = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
break;
case CAMEL_DB_COLUMN_SIZE:
mir->size = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_DSENT:
mir->dsent = cols[i] ? strtol (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_DRECEIVED:
mir->dreceived = cols[i] ? strtol (cols[i], NULL, 10) : 0;
break;
case CAMEL_DB_COLUMN_SUBJECT:
mir->subject = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_MAIL_FROM:
mir->from = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_MAIL_TO:
mir->to = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_MAIL_CC:
mir->cc = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_MLIST:
mir->mlist = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_FOLLOWUP_FLAG:
mir->followup_flag = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON:
mir->followup_completed_on = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY:
mir->followup_due_by = (gchar *) camel_pstring_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_PART:
mir->part = g_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_LABELS:
mir->labels = g_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_USERTAGS:
mir->usertags = g_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_CINFO:
mir->cinfo = g_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_BDATA:
mir->bdata = g_strdup (cols[i]);
break;
case CAMEL_DB_COLUMN_BODYSTRUCTURE:
/* Evolution itself doesn't yet use this, ignoring */
/* mir->bodystructure = g_strdup (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;
gint ret = 0;
mir = g_new0 (CamelMIRecord , 1);
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);
camel_db_camel_mir_free (mir);
return ret;
}
camel_folder_summary_unlock (summary);
info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_from_db (summary, mir);
if (info) {
if (summary->priv->build_content) {
gchar *tmp;
tmp = mir->cinfo;
/* FIXME: this should be done differently, how i don't know */
((CamelMessageInfoBase *) info)->content = perform_content_info_load_from_db (summary, mir);
if (((CamelMessageInfoBase *) info)->content == NULL) {
camel_message_info_unref (info);
info = NULL;
}
mir->cinfo = tmp;
if (!info) {
camel_db_camel_mir_free (mir);
return -1;
}
}
/* Just now we are reading from the DB, it can't be dirty. */
((CamelMessageInfoBase *) info)->dirty = FALSE;
if (data->add)
camel_folder_summary_add (summary, info);
else
camel_folder_summary_insert (summary, info, TRUE);
} else {
g_warning ("Loading messageinfo from db failed");
ret = -1;
}
camel_db_camel_mir_free (mir);
return ret;
}
/* saves the content descriptions, recursively */
static gboolean
perform_content_info_save_to_db (CamelFolderSummary *summary,
CamelMessageContentInfo *ci,
CamelMIRecord *record)
{
CamelMessageContentInfo *part;
gchar *oldr;
if (!CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_to_db (summary, ci, record))
return FALSE;
oldr = record->cinfo;
record->cinfo = g_strdup_printf ("%s %d", oldr, my_list_size ((struct _node **) &ci->childs));
g_free (oldr);
part = ci->childs;
while (part) {
if (perform_content_info_save_to_db (summary, part, record) == -1)
return FALSE;
part = part->next;
}
return TRUE;
}
static void
save_to_db_cb (gpointer key,
gpointer value,
gpointer data)
{
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) value;
CamelFolderSummary *summary = (CamelFolderSummary *) mi->summary;
CamelStore *parent_store;
const gchar *full_name;
CamelDB *cdb;
CamelMIRecord *mir;
GError **error = data;
full_name = camel_folder_get_full_name (summary->priv->folder);
parent_store = camel_folder_get_parent_store (summary->priv->folder);
cdb = parent_store->cdb_w;
if (!mi->dirty)
return;
mir = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_to_db (summary, (CamelMessageInfo *) mi);
if (mir && summary->priv->build_content) {
if (!perform_content_info_save_to_db (summary, ((CamelMessageInfoBase *) mi)->content, mir)) {
g_warning ("unable to save mir+cinfo for uid: %s\n", mir->uid);
camel_db_camel_mir_free (mir);
/* FIXME: Add exception here */
return;
}
}
g_return_if_fail (mir != NULL);
if (camel_db_write_message_info_record (cdb, full_name, mir, 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, dont unset the FOLDER_FLAGGED flag */
mi->dirty = 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;
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);
cdb = parent_store->cdb_w;
if (camel_db_prepare_message_info_table (cdb, full_name, error) != 0)
return -1;
camel_folder_summary_lock (summary);
/* Push MessageInfo-es */
camel_db_begin_transaction (cdb, NULL);
g_hash_table_foreach (summary->priv->loaded_infos, save_to_db_cb, error);
camel_db_end_transaction (cdb, NULL);
camel_folder_summary_unlock (summary);
cfs_schedule_info_release_timer (summary);
return 0;
}
static void
msg_save_preview (const gchar *uid,
gpointer value,
CamelFolder *folder)
{
CamelStore *parent_store;
const gchar *full_name;
full_name = camel_folder_get_full_name (folder);
parent_store = camel_folder_get_parent_store (folder);
camel_db_write_preview_record (
parent_store->cdb_w, full_name, uid, (gchar *) value, NULL);
}
/**
* camel_folder_summary_save_to_db:
*
* Since: 2.24
**/
gboolean
camel_folder_summary_save_to_db (CamelFolderSummary *summary,
GError **error)
{
CamelStore *parent_store;
CamelDB *cdb;
CamelFIRecord *record;
gint ret, count;
g_return_val_if_fail (summary != NULL, FALSE);
if (!(summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) ||
is_in_memory_summary (summary))
return TRUE;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
cdb = parent_store->cdb_w;
camel_folder_summary_lock (summary);
d (printf ("\ncamel_folder_summary_save_to_db called \n"));
if (summary->priv->need_preview && g_hash_table_size (summary->priv->preview_updates)) {
camel_db_begin_transaction (parent_store->cdb_w, NULL);
g_hash_table_foreach (summary->priv->preview_updates, (GHFunc) msg_save_preview, summary->priv->folder);
g_hash_table_remove_all (summary->priv->preview_updates);
camel_db_end_transaction (parent_store->cdb_w, NULL);
}
summary->flags &= ~CAMEL_FOLDER_SUMMARY_DIRTY;
count = cfs_count_dirty (summary);
if (!count) {
gboolean res = camel_folder_summary_header_save_to_db (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->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\n", 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->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
return FALSE;
}
}
record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (summary, error);
if (!record) {
summary->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->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_to_db:
*
* Since: 2.24
**/
gboolean
camel_folder_summary_header_save_to_db (CamelFolderSummary *summary,
GError **error)
{
CamelStore *parent_store;
CamelFIRecord *record;
CamelDB *cdb;
gint ret;
if (is_in_memory_summary (summary))
return TRUE;
parent_store = camel_folder_get_parent_store (summary->priv->folder);
cdb = parent_store->cdb_w;
camel_folder_summary_lock (summary);
d (printf ("\ncamel_folder_summary_header_save_to_db called \n"));
record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (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_from_db:
*
* Since: 2.24
**/
gboolean
camel_folder_summary_header_load_from_db (CamelFolderSummary *summary,
CamelStore *store,
const gchar *folder_name,
GError **error)
{
CamelDB *cdb;
CamelFIRecord *record;
gboolean ret = FALSE;
d (printf ("\ncamel_folder_summary_header_load_from_db called \n"));
if (is_in_memory_summary (summary))
return TRUE;
camel_folder_summary_lock (summary);
camel_folder_summary_save_to_db (summary, NULL);
cdb = store->cdb_r;
record = g_new0 (CamelFIRecord, 1);
camel_db_read_folder_info_record (cdb, folder_name, record, error);
ret = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_from_db (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 *uid;
CamelMessageInfo *mi;
uid = camel_message_info_uid (info);
if (uid == NULL || uid[0] == 0) {
camel_pstring_free (info->uid);
uid = info->uid = (gchar *) camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
}
camel_folder_summary_lock (summary);
while ((mi = g_hash_table_lookup (summary->priv->loaded_infos, uid))) {
camel_folder_summary_unlock (summary);
if (mi == info)
return FALSE;
d (printf ("Trying to insert message with clashing uid (%s). new uid re-assigned", camel_message_info_uid (info)));
camel_pstring_free (info->uid);
uid = info->uid = camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
camel_message_info_set_flags (info, CAMEL_MESSAGE_FOLDER_FLAGGED, CAMEL_MESSAGE_FOLDER_FLAGGED);
camel_folder_summary_lock (summary);
}
camel_folder_summary_unlock (summary);
return TRUE;
}
/**
* camel_folder_summary_add:
* @summary: a #CamelFolderSummary object
* @info: a #CamelMessageInfo
*
* Adds a new @info record to the summary. If @info->uid is %NULL,
* then a new uid is automatically re-assigned by calling
* camel_folder_summary_next_uid_string().
*
* The @info record should have been generated by calling one of the
* info_new_*() functions, as it will be free'd based on the summary
* class. And MUST NOT be allocated directly using malloc.
**/
void
camel_folder_summary_add (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
CamelMessageInfoBase *base_info;
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (info == NULL)
return;
camel_folder_summary_lock (summary);
if (!summary_assign_uid (summary, info)) {
camel_folder_summary_unlock (summary);
return;
}
base_info = (CamelMessageInfoBase *) info;
folder_summary_update_counts_by_flags (summary, camel_message_info_flags (info), UPDATE_COUNTS_ADD);
base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
base_info->dirty = TRUE;
g_hash_table_insert (
summary->priv->uids,
(gpointer) camel_pstring_strdup (camel_message_info_uid (info)),
GUINT_TO_POINTER (camel_message_info_flags (info)));
/* Summary always holds a ref for the loaded infos */
g_hash_table_insert (summary->priv->loaded_infos, (gpointer) camel_message_info_uid (info), info);
camel_folder_summary_touch (summary);
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_insert:
*
* Since: 2.24
**/
void
camel_folder_summary_insert (CamelFolderSummary *summary,
CamelMessageInfo *info,
gboolean load)
{
g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
if (info == NULL)
return;
camel_folder_summary_lock (summary);
if (!load) {
CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
folder_summary_update_counts_by_flags (summary, camel_message_info_flags (info), UPDATE_COUNTS_ADD);
base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
base_info->dirty = TRUE;
g_hash_table_insert (
summary->priv->uids,
(gpointer) camel_pstring_strdup (camel_message_info_uid (info)),
GUINT_TO_POINTER (camel_message_info_flags (info)));
camel_folder_summary_touch (summary);
}
/* Summary always holds a ref for the loaded infos */
g_hash_table_insert (summary->priv->loaded_infos, (gchar *) camel_message_info_uid (info), info);
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_info_new_from_header:
* @summary: a #CamelFolderSummary object
* @headers: rfc822 headers
*
* Create a new info record from a header.
*
* Returns: the newly allocated record which must be unreferenced with
* camel_message_info_unref()
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_header (CamelFolderSummary *summary,
struct _camel_header_raw *h)
{
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->message_info_new_from_header != NULL, NULL);
return class->message_info_new_from_header (summary, h);
}
/**
* camel_folder_summary_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: the newly allocated record which must be unreferenced with
* camel_message_info_unref()
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_parser (CamelFolderSummary *summary,
CamelMimeParser *mp)
{
CamelMessageInfo *info = NULL;
gchar *buffer;
gsize len;
CamelFolderSummaryPrivate *p = summary->priv;
goffset start;
CamelIndexName *name = 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 = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->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 (p->index)
summary_assign_uid (summary, info);
g_rec_mutex_lock (&summary->priv->filter_lock);
if (p->index) {
if (p->filter_index == NULL)
p->filter_index = camel_mime_filter_index_new (p->index);
camel_index_delete_name (p->index, camel_message_info_uid (info));
name = camel_index_add_name (p->index, camel_message_info_uid (info));
camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
}
/* always scan the content info, even if we dont save it */
((CamelMessageInfoBase *) info)->content = summary_build_content_info (summary, info, mp);
if (name && p->index) {
camel_index_write_name (p->index, name);
g_object_unref (name);
camel_mime_filter_index_set_name (
CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
}
g_rec_mutex_unlock (&summary->priv->filter_lock);
((CamelMessageInfoBase *) info)->size = camel_mime_parser_tell (mp) - start;
}
return info;
}
/**
* camel_folder_summary_info_new_from_message:
* @summary: a #CamelFolderSummary object
* @message: a #CamelMimeMessage object
* @bodystructure: a bodystructure or NULL
*
* Create a summary item from a message.
*
* Returns: the newly allocated record which must be unreferenced with
* camel_message_info_unref()
**/
CamelMessageInfo *
camel_folder_summary_info_new_from_message (CamelFolderSummary *summary,
CamelMimeMessage *msg,
const gchar *bodystructure)
{
CamelMessageInfo *info;
CamelFolderSummaryPrivate *p = summary->priv;
CamelIndexName *name = NULL;
info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_message (summary, msg, bodystructure);
/* 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 (p->index)
summary_assign_uid (summary, info);
g_rec_mutex_lock (&summary->priv->filter_lock);
if (p->index) {
if (p->filter_index == NULL)
p->filter_index = camel_mime_filter_index_new (p->index);
camel_index_delete_name (p->index, camel_message_info_uid (info));
name = camel_index_add_name (p->index, camel_message_info_uid (info));
camel_mime_filter_index_set_name (
CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
if (p->filter_stream == NULL) {
CamelStream *null = camel_stream_null_new ();
p->filter_stream = camel_stream_filter_new (null);
g_object_unref (null);
}
}
((CamelMessageInfoBase *) info)->content = summary_build_content_info_message (summary, info, (CamelMimePart *) msg);
if (name) {
camel_index_write_name (p->index, name);
g_object_unref (name);
camel_mime_filter_index_set_name (
CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
}
g_rec_mutex_unlock (&summary->priv->filter_lock);
return info;
}
/**
* camel_folder_summary_content_info_free:
* @summary: a #CamelFolderSummary object
* @ci: a #CamelMessageContentInfo
*
* Free the content info @ci, and all associated memory.
**/
void
camel_folder_summary_content_info_free (CamelFolderSummary *summary,
CamelMessageContentInfo *ci)
{
CamelMessageContentInfo *pw, *pn;
pw = ci->childs;
CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_free (summary, ci);
while (pw) {
pn = pw->next;
camel_folder_summary_content_info_free (summary, pw);
pw = pn;
}
}
/**
* 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->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
camel_folder_summary_unlock (summary);
}
/**
* camel_folder_summary_clear:
* @summary: a #CamelFolderSummary object
*
* Empty the summary contents.
**/
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);
cdb = parent_store->cdb_w;
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);
if (camel_folder_summary_remove_uid (summary, camel_message_info_uid (info))) {
camel_message_info_unref (info);
return TRUE;
}
return FALSE;
}
/**
* 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;
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);
g_hash_table_remove (summary->priv->loaded_infos, 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 (camel_db_delete_uid (parent_store->cdb_w, 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: 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)
camel_message_info_unref (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 (camel_db_delete_uids (parent_store->cdb_w, 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;
}
static struct _node *
my_list_append (struct _node **list,
struct _node *n)
{
struct _node *ln = *list;
n->next = NULL;
if (!ln) {
*list = n;
return n;
}
while (ln->next)
ln = ln->next;
ln->next = n;
return n;
}
static gint
my_list_size (struct _node **list)
{
gint len = 0;
struct _node *ln = (struct _node *) list;
while (ln->next) {
ln = ln->next;
len++;
}
return len;
}
/* are these even useful for anything??? */
static CamelMessageInfo *
message_info_new_from_parser (CamelFolderSummary *summary,
CamelMimeParser *mp)
{
CamelMessageInfo *mi = NULL;
gint state;
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:
mi = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
break;
default:
g_error ("Invalid parser state");
}
return mi;
}
static CamelMessageContentInfo *
content_info_new_from_parser (CamelFolderSummary *summary,
CamelMimeParser *mp)
{
CamelMessageContentInfo *ci = NULL;
switch (camel_mime_parser_state (mp)) {
case CAMEL_MIME_PARSER_STATE_HEADER:
case CAMEL_MIME_PARSER_STATE_MESSAGE:
case CAMEL_MIME_PARSER_STATE_MULTIPART:
ci = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
if (ci) {
if (ci->type)
camel_content_type_unref (ci->type);
ci->type = camel_mime_parser_content_type (mp);
camel_content_type_ref (ci->type);
}
break;
default:
g_error ("Invalid parser state");
}
return ci;
}
static CamelMessageInfo *
message_info_new_from_message (CamelFolderSummary *summary,
CamelMimeMessage *msg,
const gchar *bodystructure)
{
CamelMessageInfo *mi;
mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->message_info_new_from_header (summary, ((CamelMimePart *) msg)->headers);
((CamelMessageInfoBase *) mi)->bodystructure = g_strdup (bodystructure);
return mi;
}
static CamelMessageContentInfo *
content_info_new_from_message (CamelFolderSummary *summary,
CamelMimePart *mp)
{
CamelMessageContentInfo *ci;
ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->content_info_new_from_header (summary, mp->headers);
return ci;
}
static gchar *
summary_format_address (struct _camel_header_raw *h,
const gchar *name,
const gchar *charset)
{
struct _camel_header_address *addr;
gchar *text, *str;
if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
return NULL;
while (isspace ((unsigned) *text))
text++;
text = camel_header_unfold (text);
if ((addr = camel_header_address_decode (text, charset))) {
str = camel_header_address_list_format (addr);
camel_header_address_list_clear (&addr);
g_free (text);
} else {
str = text;
}
return str;
}
static gchar *
summary_format_string (struct _camel_header_raw *h,
const gchar *name,
const gchar *charset)
{
gchar *text, *str;
if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
return NULL;
while (isspace ((unsigned) *text))
text++;
text = camel_header_unfold (text);
str = camel_header_decode_string (text, charset);
g_free (text);
return str;
}
/**
* camel_folder_summary_content_info_new:
* @summary: a #CamelFolderSummary object
*
* Allocate a new #CamelMessageContentInfo, suitable for adding
* to this summary.
*
* Returns: a newly allocated #CamelMessageContentInfo
**/
CamelMessageContentInfo *
camel_folder_summary_content_info_new (CamelFolderSummary *summary)
{
CamelFolderSummaryClass *class;
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (class->content_info_size > 0, NULL);
return g_slice_alloc0 (class->content_info_size);
}
static CamelMessageInfo *
message_info_new_from_header (CamelFolderSummary *summary,
struct _camel_header_raw *h)
{
const gchar *received, *date, *content, *charset = NULL;
struct _camel_header_references *refs, *irt, *scan;
gchar *subject, *from, *to, *cc, *mlist;
CamelContentType *ct = NULL;
CamelMessageInfoBase *mi;
guint8 *digest;
gsize length;
gchar *msgid;
gint count;
length = g_checksum_type_get_length (G_CHECKSUM_MD5);
digest = g_alloca (length);
mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
if ((content = camel_header_raw_find (&h, "Content-Type", NULL))
&& (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 (h, "subject", charset);
from = summary_format_address (h, "from", charset);
to = summary_format_address (h, "to", charset);
cc = summary_format_address (h, "cc", charset);
mlist = camel_header_raw_check_mailing_list (&h);
if (ct)
camel_content_type_unref (ct);
mi->subject = camel_pstring_add (subject, TRUE);
mi->from = camel_pstring_add (from, TRUE);
mi->to = camel_pstring_add (to, TRUE);
mi->cc = camel_pstring_add (cc, TRUE);
mi->mlist = camel_pstring_add (mlist, TRUE);
mi->user_flags = NULL;
mi->user_tags = NULL;
if ((date = camel_header_raw_find (&h, "date", NULL)))
mi->date_sent = camel_header_decode_date (date, NULL);
else
mi->date_sent = 0;
received = camel_header_raw_find (&h, "received", NULL);
if (received)
received = strrchr (received, ';');
if (received)
mi->date_received = camel_header_decode_date (received + 1, NULL);
else
mi->date_received = 0;
msgid = camel_header_msgid_decode (camel_header_raw_find (&h, "message-id", NULL));
if (msgid) {
GChecksum *checksum;
checksum = g_checksum_new (G_CHECKSUM_MD5);
g_checksum_update (checksum, (guchar *) msgid, -1);
g_checksum_get_digest (checksum, digest, &length);
g_checksum_free (checksum);
memcpy (mi->message_id.id.hash, digest, sizeof (mi->message_id.id.hash));
g_free (msgid);
}
/* decode our references and in-reply-to headers */
refs = camel_header_references_decode (camel_header_raw_find (&h, "references", NULL));
irt = camel_header_references_inreplyto_decode (camel_header_raw_find (&h, "in-reply-to", NULL));
if (refs || irt) {
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 = camel_header_references_list_size (&refs);
mi->references = g_malloc (sizeof (*mi->references) + ((count - 1) * sizeof (mi->references->references[0])));
count = 0;
scan = refs;
while (scan) {
GChecksum *checksum;
checksum = g_checksum_new (G_CHECKSUM_MD5);
g_checksum_update (checksum, (guchar *) scan->id, -1);
g_checksum_get_digest (checksum, digest, &length);
g_checksum_free (checksum);
memcpy (mi->references->references[count].id.hash, digest, sizeof (mi->message_id.id.hash));
count++;
scan = scan->next;
}
mi->references->size = count;
camel_header_references_list_clear (&refs);
}
return (CamelMessageInfo *) mi;
}
static void
message_info_free (CamelFolderSummary *summary,
CamelMessageInfo *info)
{
CamelFolderSummaryClass *class;
CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
if (mi->uid) {
if (summary) {
camel_folder_summary_lock (summary);
if (g_hash_table_lookup (summary->priv->loaded_infos, mi->uid) == mi) {
g_hash_table_remove (summary->priv->loaded_infos, mi->uid);
}
camel_folder_summary_unlock (summary);
}
camel_pstring_free (mi->uid);
}
camel_pstring_free (mi->subject);
camel_pstring_free (mi->from);
camel_pstring_free (mi->to);
camel_pstring_free (mi->cc);
camel_pstring_free (mi->mlist);
g_free (mi->bodystructure);
g_free (mi->references);
g_free (mi->preview);
camel_flag_list_free (&mi->user_flags);
camel_tag_list_free (&mi->user_tags);
if (mi->headers)
camel_header_param_list_free (mi->headers);
if (summary) {
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_slice_free1 (class->message_info_size, mi);
} else
g_slice_free (CamelMessageInfoBase, mi);
}
static CamelMessageContentInfo *
content_info_new_from_header (CamelFolderSummary *summary,
struct _camel_header_raw *h)
{
CamelMessageContentInfo *ci;
const gchar *charset;
ci = camel_folder_summary_content_info_new (summary);
charset = camel_iconv_locale_charset ();
ci->id = camel_header_msgid_decode (camel_header_raw_find (&h, "content-id", NULL));
ci->description = camel_header_decode_string (camel_header_raw_find (&h, "content-description", NULL), charset);
ci->encoding = camel_content_transfer_encoding_decode (camel_header_raw_find (&h, "content-transfer-encoding", NULL));
ci->type = camel_content_type_decode (camel_header_raw_find (&h, "content-type", NULL));
return ci;
}
static void
content_info_free (CamelFolderSummary *summary,
CamelMessageContentInfo *ci)
{
CamelFolderSummaryClass *class;
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
camel_content_type_unref (ci->type);
g_free (ci->id);
g_free (ci->description);
g_free (ci->encoding);
g_slice_free1 (class->content_info_size, ci);
}
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 CamelMessageContentInfo *
summary_build_content_info (CamelFolderSummary *summary,
CamelMessageInfo *msginfo,
CamelMimeParser *mp)
{
gint state;
gsize len;
gchar *buffer;
CamelMessageContentInfo *info = NULL;
CamelContentType *ct;
gint enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
CamelFolderSummaryPrivate *p = summary->priv;
CamelMimeFilter *mfc;
CamelMessageContentInfo *part;
const gchar *calendar_header;
d (printf ("building content info\n"));
/* start of this part */
state = camel_mime_parser_step (mp, &buffer, &len);
if (summary->priv->build_content)
info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_parser (summary, mp);
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", "x-pkcs7-signature")
|| camel_content_type_is (ct, "application", "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 (p->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 (p->filter_64 == NULL)
p->filter_64 = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
else
camel_mime_filter_reset (p->filter_64);
enc_id = camel_mime_parser_filter_add (mp, p->filter_64);
} else if (!g_ascii_strcasecmp (encoding, "quoted-printable")) {
d (printf (" decoding quoted-printable\n"));
if (p->filter_qp == NULL)
p->filter_qp = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_DEC);
else
camel_mime_filter_reset (p->filter_qp);
enc_id = camel_mime_parser_filter_add (mp, p->filter_qp);
} else if (!g_ascii_strcasecmp (encoding, "x-uuencode")) {
d (printf (" decoding x-uuencode\n"));
if (p->filter_uu == NULL)
p->filter_uu = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
else
camel_mime_filter_reset (p->filter_uu);
enc_id = camel_mime_parser_filter_add (mp, p->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 (p->filter_charset, charset);
if (mfc == NULL) {
mfc = camel_mime_filter_charset_new (charset, "UTF-8");
if (mfc)
g_hash_table_insert (p->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 (p->filter_html == NULL)
p->filter_html = camel_mime_filter_html_new ();
else
camel_mime_filter_reset ((CamelMimeFilter *) p->filter_html);
html_id = camel_mime_parser_filter_add (mp, (CamelMimeFilter *) p->filter_html);
}
/* and this filter actually does the indexing */
idx_id = camel_mime_parser_filter_add (mp, p->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);
part = summary_build_content_info (summary, msginfo, mp);
if (part) {
part->parent = info;
my_list_append ((struct _node **) &info->childs, (struct _node *) part);
}
}
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);
part = summary_build_content_info (summary, msginfo, mp);
if (part) {
part->parent = info;
my_list_append ((struct _node **) &info->childs, (struct _node *) part);
}
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 building content info\n"));
return info;
}
/* build the content-info, from a message */
/* this needs the filter lock since it uses filters to perform indexing */
static CamelMessageContentInfo *
summary_build_content_info_message (CamelFolderSummary *summary,
CamelMessageInfo *msginfo,
CamelMimePart *object)
{
CamelDataWrapper *containee;
gint parts, i;
CamelFolderSummaryPrivate *p = summary->priv;
CamelMessageContentInfo *info = NULL, *child;
CamelContentType *ct;
const struct _camel_header_raw *header;
if (summary->priv->build_content)
info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_message (summary, object);
containee = camel_medium_get_content (CAMEL_MEDIUM (object));
if (containee == NULL)
return info;
/* 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 = ((CamelDataWrapper *) containee)->mime_type;
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", "x-pkcs7-signature")
|| camel_content_type_is (ct, "application", "pkcs7-signature")
#endif
) {
camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
}
for (header = object->headers; header; header = header->next) {
const gchar *value = header->value;
/* skip preceding spaces in the value */
while (value && *value && 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)))
break;
}
if (header || camel_content_type_is (ct, "text", "calendar"))
camel_message_info_set_user_flag (msginfo, "$has_cal", 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_val_if_fail (part, info);
child = summary_build_content_info_message (summary, msginfo, part);
if (child) {
child->parent = info;
my_list_append ((struct _node **) &info->childs, (struct _node *) child);
}
}
} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
/* for messages we only look at its contents */
child = summary_build_content_info_message (summary, msginfo, (CamelMimePart *) containee);
if (child) {
child->parent = info;
my_list_append ((struct _node **) &info->childs, (struct _node *) child);
}
} else if (p->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 (p->filter_html == NULL)
p->filter_html = camel_mime_filter_html_new ();
else
camel_mime_filter_reset ((CamelMimeFilter *) p->filter_html);
html_id = camel_stream_filter_add (
CAMEL_STREAM_FILTER (p->filter_stream),
(CamelMimeFilter *) p->filter_html);
}
idx_id = camel_stream_filter_add (
CAMEL_STREAM_FILTER (p->filter_stream),
p->filter_index);
/* FIXME Pass a GCancellable and GError here. */
camel_data_wrapper_decode_to_stream_sync (
containee, p->filter_stream, NULL, NULL);
camel_stream_flush (p->filter_stream, NULL, NULL);
camel_stream_filter_remove (
CAMEL_STREAM_FILTER (p->filter_stream), idx_id);
camel_stream_filter_remove (
CAMEL_STREAM_FILTER (p->filter_stream), html_id);
}
return info;
}
/**
* camel_flag_get:
* @list: the address of a #CamelFlag list
* @name: name of the flag to get
*
* Find the state of the flag @name in @list.
*
* Returns: the state of the flag (%TRUE or %FALSE)
**/
gboolean
camel_flag_get (CamelFlag **list,
const gchar *name)
{
CamelFlag *flag;
flag = *list;
while (flag) {
if (!strcmp (flag->name, name))
return TRUE;
flag = flag->next;
}
return FALSE;
}
/**
* camel_flag_set:
* @list: the address of a #CamelFlag list
* @name: name of the flag to set or change
* @value: the value to set on the flag
*
* Set the state of a flag @name in the list @list to @value.
*
* Returns: %TRUE if the value of the flag has been changed or %FALSE
* otherwise
**/
gboolean
camel_flag_set (CamelFlag **list,
const gchar *name,
gboolean value)
{
CamelFlag *flag, *tmp;
gsize tmp_len = 0;
if (!name)
return TRUE;
/* this 'trick' works because flag->next is the first element */
flag = (CamelFlag *) list;
while (flag->next) {
tmp = flag->next;
if (!strcmp (flag->next->name, name)) {
if (!value) {
flag->next = tmp->next;
g_free (tmp);
}
return !value;
}
flag = tmp;
}
if (value) {
tmp_len = sizeof (*tmp) + strlen (name);
tmp = g_malloc (tmp_len);
g_strlcpy (tmp->name, name, strlen (name) + 1);
tmp->next = NULL;
flag->next = tmp;
}
return value;
}
/**
* camel_flag_list_size:
* @list: the address of a #CamelFlag list
*
* Get the length of the flag list.
*
* Returns: the number of flags in the list
**/
gint
camel_flag_list_size (CamelFlag **list)
{
gint count = 0;
CamelFlag *flag;
flag = *list;
while (flag) {
count++;
flag = flag->next;
}
return count;
}
/**
* camel_flag_list_free:
* @list: the address of a #CamelFlag list
*
* Free the memory associated with the flag list @list.
**/
void
camel_flag_list_free (CamelFlag **list)
{
CamelFlag *flag, *tmp;
flag = *list;
while (flag) {
tmp = flag->next;
g_free (flag);
flag = tmp;
}
*list = NULL;
}
/**
* camel_flag_list_copy:
* @to: the address of the #CamelFlag list to copy to
* @from: the address of the #CamelFlag list to copy from
*
* Copy a flag list.
*
* Returns: %TRUE if @to is changed or %FALSE otherwise
**/
gboolean
camel_flag_list_copy (CamelFlag **to,
CamelFlag **from)
{
CamelFlag *flag, *tmp;
gboolean changed = FALSE;
if (*to == NULL && from == NULL)
return FALSE;
/* Remove any now-missing flags */
flag = (CamelFlag *) to;
while (flag->next) {
tmp = flag->next;
if (!camel_flag_get (from, tmp->name)) {
if (*tmp->name)
changed = TRUE;
flag->next = tmp->next;
g_free (tmp);
} else {
flag = tmp;
}
}
/* Add any new non-empty flags */
flag = *from;
while (flag) {
if (*flag->name)
changed |= camel_flag_set (to, flag->name, TRUE);
flag = flag->next;
}
return changed;
}
/**
* camel_tag_get:
* @list: the address of a #CamelTag list
* @name: name of the tag to get
*
* Find the flag @name in @list and get the value.
*
* Returns: the value of the flag or %NULL if unset
**/
const gchar *
camel_tag_get (CamelTag **list,
const gchar *name)
{
CamelTag *tag;
tag = *list;
while (tag) {
if (!strcmp (tag->name, name))
return (const gchar *) tag->value;
tag = tag->next;
}
return NULL;
}
/**
* camel_tag_set:
* @list: the address of a #CamelTag list
* @name: name of the tag to set
* @value: value to set on the tag
*
* Set the tag @name in the tag list @list to @value.
*
* Returns: %TRUE if the value on the tag changed or %FALSE otherwise
**/
gboolean
camel_tag_set (CamelTag **list,
const gchar *name,
const gchar *value)
{
CamelTag *tag, *tmp;
/* this 'trick' works because tag->next is the first element */
tag = (CamelTag *) list;
while (tag->next) {
tmp = tag->next;
if (!strcmp (tmp->name, name)) {
if (value == NULL) { /* clear it? */
tag->next = tmp->next;
g_free (tmp->value);
g_free (tmp);
return TRUE;
} else if (strcmp (tmp->value, value)) { /* has it changed? */
g_free (tmp->value);
tmp->value = g_strdup (value);
return TRUE;
}
return FALSE;
}
tag = tmp;
}
if (value) {
tmp = g_malloc (sizeof (*tmp) + strlen (name));
g_strlcpy (tmp->name, name, strlen (name) + 1);
tmp->value = g_strdup (value);
tmp->next = NULL;
tag->next = tmp;
return TRUE;
}
return FALSE;
}
/**
* camel_tag_list_size:
* @list: the address of a #CamelTag list
*
* Get the number of tags present in the tag list @list.
*
* Returns: the number of tags
**/
gint
camel_tag_list_size (CamelTag **list)
{
gint count = 0;
CamelTag *tag;
tag = *list;
while (tag) {
count++;
tag = tag->next;
}
return count;
}
static void
rem_tag (gchar *key,
gchar *value,
CamelTag **to)
{
camel_tag_set (to, key, NULL);
}
/**
* camel_tag_list_copy:
* @to: the address of the #CamelTag list to copy to
* @from: the address of the #CamelTag list to copy from
*
* Copy a tag list.
*
* Returns: %TRUE if @to is changed or %FALSE otherwise
**/
gboolean
camel_tag_list_copy (CamelTag **to,
CamelTag **from)
{
gint changed = FALSE;
CamelTag *tag;
GHashTable *left;
if (*to == NULL && from == NULL)
return FALSE;
left = g_hash_table_new (g_str_hash, g_str_equal);
tag = *to;
while (tag) {
g_hash_table_insert (left, tag->name, tag);
tag = tag->next;
}
tag = *from;
while (tag) {
changed |= camel_tag_set (to, tag->name, tag->value);
g_hash_table_remove (left, tag->name);
tag = tag->next;
}
if (g_hash_table_size (left) > 0) {
g_hash_table_foreach (left, (GHFunc) rem_tag, to);
changed = TRUE;
}
g_hash_table_destroy (left);
return changed;
}
/**
* camel_tag_list_free:
* @list: the address of a #CamelTag list
*
* Free the tag list @list.
**/
void
camel_tag_list_free (CamelTag **list)
{
CamelTag *tag, *tmp;
tag = *list;
while (tag) {
tmp = tag->next;
g_free (tag->value);
g_free (tag);
tag = tmp;
}
*list = NULL;
}
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 },
{ 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:
* @summary: a #CamelFolderSummary object or %NULL
*
* Create a new #CamelMessageInfo.
*
* Returns: a new #CamelMessageInfo
**/
gpointer
camel_message_info_new (CamelFolderSummary *summary)
{
CamelFolderSummaryClass *class;
CamelMessageInfo *info;
gsize message_info_size;
if (summary != NULL) {
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
g_return_val_if_fail (class->message_info_size > 0, NULL);
message_info_size = class->message_info_size;
} else {
message_info_size = sizeof (CamelMessageInfoBase);
}
info = g_slice_alloc0 (message_info_size);
info->refcount = 1;
info->summary = summary;
/* We assume that mi is always dirty unless freshly read or just saved*/
((CamelMessageInfoBase *) info)->dirty = TRUE;
return info;
}
/**
* camel_message_info_ref:
* @info: a #CamelMessageInfo
*
* Reference an info.
**/
gpointer
camel_message_info_ref (gpointer o)
{
CamelMessageInfo *mi = o;
g_return_val_if_fail (mi != NULL, NULL);
g_return_val_if_fail (mi->refcount > 0, NULL);
g_atomic_int_inc (&mi->refcount);
return o;
}
/**
* camel_message_info_new_from_header:
* @summary: a #CamelFolderSummary object or %NULL
* @header: raw header
*
* Create a new #CamelMessageInfo pre-populated with info from
* @header.
*
* Returns: a new #CamelMessageInfo
**/
CamelMessageInfo *
camel_message_info_new_from_header (CamelFolderSummary *summary,
struct _camel_header_raw *header)
{
if (summary != NULL)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->
message_info_new_from_header (summary, header);
else
return message_info_new_from_header (NULL, header);
}
/**
* camel_message_info_unref:
* @info: a #CamelMessageInfo
*
* Unref's and potentially frees a #CamelMessageInfo and its contents.
**/
void
camel_message_info_unref (gpointer o)
{
CamelMessageInfo *mi = o;
g_return_if_fail (mi != NULL);
g_return_if_fail (mi->refcount > 0);
if (g_atomic_int_dec_and_test (&mi->refcount)) {
if (mi->summary != NULL) {
CamelFolderSummaryClass *class;
/* FIXME This is kinda busted, should really
* be handled by message_info_free(). */
if (mi->summary->priv->build_content
&& ((CamelMessageInfoBase *) mi)->content) {
camel_folder_summary_content_info_free (
mi->summary,
((CamelMessageInfoBase *) mi)->content);
((CamelMessageInfoBase *) mi)->content = NULL;
}
class = CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary);
g_return_if_fail (class->message_info_free != NULL);
class->message_info_free (mi->summary, mi);
} else {
message_info_free (NULL, mi);
}
}
}
/**
* camel_message_info_clone:
* @info: a #CamelMessageInfo
*
* Duplicate a #CamelMessageInfo.
*
* Returns: the duplicated #CamelMessageInfo
**/
gpointer
camel_message_info_clone (gconstpointer o)
{
const CamelMessageInfo *mi = o;
if (mi->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->message_info_clone (mi->summary, mi);
else
return message_info_clone (NULL, mi);
}
/**
* camel_message_info_ptr:
* @info: a #CamelMessageInfo
* @id: info to get
*
* Generic accessor method for getting pointer data.
*
* Returns: the pointer data
**/
gconstpointer
camel_message_info_ptr (const CamelMessageInfo *info,
gint id)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_ptr (info, id);
else
return info_ptr (info, id);
}
/**
* camel_message_info_uint32:
* @info: a #CamelMessageInfo
* @id: info to get
*
* Generic accessor method for getting 32bit gint data.
*
* Returns: the gint data
**/
guint32
camel_message_info_uint32 (const CamelMessageInfo *info,
gint id)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_uint32 (info, id);
else
return info_uint32 (info, id);
}
/**
* camel_message_info_time:
* @info: a #CamelMessageInfo
* @id: info to get
*
* Generic accessor method for getting time_t data.
*
* Returns: the time_t data
**/
time_t
camel_message_info_time (const CamelMessageInfo *info,
gint id)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_time (info, id);
else
return info_time (info, id);
}
/**
* camel_message_info_user_flag:
* @info: a #CamelMessageInfo
* @id: user flag to get
*
* Get the state of a user flag named @id.
*
* Returns: the state of the user flag
**/
gboolean
camel_message_info_user_flag (const CamelMessageInfo *info,
const gchar *id)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_user_flag (info, id);
else
return info_user_flag (info, id);
}
/**
* camel_message_info_user_tag:
* @info: a #CamelMessageInfo
* @id: user tag to get
*
* Get the value of a user tag named @id.
*
* Returns: the value of the user tag
**/
const gchar *
camel_message_info_user_tag (const CamelMessageInfo *info,
const gchar *id)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_user_tag (info, id);
else
return info_user_tag (info, id);
}
/**
* camel_message_info_set_flags:
* @info: a #CamelMessageInfo
* @flags: mask of flags to change
* @set: state the flags should be changed to
*
* Change the state of the system flags on the #CamelMessageInfo
*
* Returns: %TRUE if any of the flags changed or %FALSE otherwise
**/
gboolean
camel_message_info_set_flags (CamelMessageInfo *info,
CamelMessageFlags flags,
guint32 set)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_flags (info, flags, set);
else
return info_set_flags (info, flags, set);
}
/**
* camel_message_info_set_user_flag:
* @info: a #CamelMessageInfo
* @id: name of the user flag to set
* @state: state to set the flag to
*
* Set the state of a user flag on a #CamelMessageInfo.
*
* Returns: %TRUE if the state changed or %FALSE otherwise
**/
gboolean
camel_message_info_set_user_flag (CamelMessageInfo *info,
const gchar *id,
gboolean state)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_user_flag (info, id, state);
else
return info_set_user_flag (info, id, state);
}
/**
* camel_message_info_set_user_tag:
* @info: a #CamelMessageInfo
* @id: name of the user tag to set
* @val: value to set
*
* Set the value of a user tag on a #CamelMessageInfo.
*
* Returns: %TRUE if the value changed or %FALSE otherwise
**/
gboolean
camel_message_info_set_user_tag (CamelMessageInfo *info,
const gchar *id,
const gchar *val)
{
if (info->summary)
return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_user_tag (info, id, val);
else
return info_set_user_tag (info, id, val);
}
void
camel_content_info_dump (CamelMessageContentInfo *ci,
gint depth)
{
gchar *p;
p = alloca (depth * 4 + 1);
memset (p, ' ', depth * 4);
p[depth * 4] = 0;
if (ci == NULL) {
printf ("%s\n", p);
return;
}
if (ci->type)
printf (
"%scontent-type: %s/%s\n",
p, ci->type->type ? ci->type->type : "(null)",
ci->type->subtype ? ci->type->subtype : "(null)");
else
printf ("%scontent-type: \n", p);
printf (
"%scontent-transfer-encoding: %s\n",
p, ci->encoding ? ci->encoding : "(null)");
printf (
"%scontent-description: %s\n",
p, ci->description ? ci->description : "(null)");
printf ("%ssize: %lu\n", p, (gulong) ci->size);
ci = ci->childs;
while (ci) {
camel_content_info_dump (ci, depth + 1);
ci = ci->next;
}
}
void
camel_message_info_dump (CamelMessageInfo *info)
{
if (info == NULL) {
printf ("No message?\n");
return;
}
printf ("Subject: %s\n", camel_message_info_subject (info));
printf ("To: %s\n", camel_message_info_to (info));
printf ("Cc: %s\n", camel_message_info_cc (info));
printf ("mailing list: %s\n", camel_message_info_mlist (info));
printf ("From: %s\n", camel_message_info_from (info));
printf ("UID: %s\n", camel_message_info_uid (info));
printf ("Flags: %04x\n", camel_message_info_flags (info));
camel_content_info_dump (((CamelMessageInfoBase *) info)->content, 0);
}
/**
* 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);
}
gint
bdata_extract_digit (/* const */ gchar **part)
{
if (!part || !*part || !**part)
return 0;
if (**part == ' ')
*part += 1;
if (!**part)
return 0;
return strtoul (*part, part, 10);
}
/* expecting "digit-value", where digit is length of the value */
gchar *
bdata_extract_string (/* const */ gchar **part)
{
gint len, has_len;
gchar *val;
len = bdata_extract_digit (part);
/* might be a '-' sign */
if (part && *part && **part)
*part += 1;
if (len <= 0 || !part || !*part || !**part)
return g_strdup ("");
if (!**part)
return g_strdup ("");
has_len = strlen (*part);
if (has_len < len)
len = has_len;
val = g_strndup (*part, len);
*part += len;
return val;
}