Files
evolution/camel/camel-folder-summary.c
Not Zed 1fbfdbd43e Remove assertion that content is there, when it no longer can be.
2000-11-30  Not Zed  <NotZed@HelixCode.com>

	* providers/local/camel-mbox-folder.c (mbox_get_message): Remove
	assertion that content is there, when it no longer can be.

	* camel-folder-summary.h: Removed pos/bodypos/endpos from
	camelmeessagecontentinfo.
	(CamelMessageFlags): Added an attachments flag.

	* providers/local/camel-local-summary.h: Added load virtual
	function.

	* tests/lib/folders.c (test_message_info): Accessors.
	(test_folder_message): "

	* camel-folder-thread.c (get_root_subject): Fix accessors.
	(dump_tree_rec): "

	* camel-folder-search.c (camel_folder_search_execute_expression):
	Accessors for messageinfo.
	(search_match_all): "
	(search_header_contains): "
	(search_header_contains): "
	(search_body_contains): "
	(camel_folder_search_execute_expression): Use mepool_strdup.

	* providers/local/camel-mbox-summary.c (summary_update): Accessors
	for messageinfo.
	(mbox_summary_sync_full): "

	* providers/local/camel-mh-summary.c (remove_summary): Accessors
	for messageinfo.
	(mh_summary_check): "
	(mh_summary_sync_message): "
	(mh_summary_sync): "

	* providers/local/camel-mh-folder.c (mh_append_message): Use
	accessor for uid.

	* providers/local/camel-local-summary.c
	(local_summary_decode_x_evolution): Use accessor to uid.
	(local_summary_encode_x_evolution): Likewise.
	(message_info_new): And here.
	(camel_local_summary_load): Call virtual load function.
	(local_summary_load): Default load function, load summary.
	(camel_local_summary_load): Check file exists before trying to
	load.
	(camel_local_summary_construct): Turn off building content info!
	(CAMEL_LOCAL_SUMMARY_VERSION): Bump, since we dont build content
	info anymore.
	(camel_local_summary_load): After a successful load/check, do a
	save too so we dont have to go through it again randomly.

	* providers/nntp/camel-nntp-utils.c (get_XOVER_headers): Use
	accessors for messageinfo.

	* providers/nntp/camel-nntp-folder.c (nntp_folder_get_uids): Use
	accessors for uid.

	* providers/imap/camel-imap-folder.c (imap_refresh_info): Use
	accessor for uid.
	(imap_sync): Likewise.
	(imap_get_uids): Likewise.
	(imap_update_summary): And here.

	* providers/vee/camel-vee-folder.c (vfolder_remove_match): Use
	accessor for uid.
	(vfolder_add_match): Handle estrv stuff.
	(vfolder_change_match): Accessor for uid.
	(get_real_message): "
	(vee_get_uids): "
	(vee_folder_build): " + estrv.
	(vee_folder_build_folder): "

	* providers/local/camel-maildir-folder.c (maildir_append_message):
	Use acccessors for uid's.
	(maildir_get_message): Here too.

	* providers/local/camel-maildir-summary.c
	(camel_maildir_summary_init): Setup the string count for us.
	(message_info_new): Access the string array directly.
	(message_info_free): No need to free string if using array.
	(camel_maildir_summary_info_to_name): Use accessor to get to uid.
	(remove_summary): And here.
	(maildir_summary_check): Likewise.
	(maildir_summary_sync): And here.
	(maildir_summary_load): Load up a cache of uid->filename mappings
	before loading the actual summary file.  This saves us having to
	waste the diskspace storing the filenames in the summary itself,
	and also helps us sync the summary better on load.
	(message_info_load): If we have the load_map setup, and the uid
	exists, then set the filename cache from it, and update the flags
	from the name, incase our summary mismatches it.

	* camel-folder-summary.c (camel_folder_summary_init): Setup string
	count for compressed info record.  An optional compile mode which
	stores all strings for a given messageinfo into a packed array,
	which should save 36-50 bytes/record.
	(camel_folder_summary_info_new): Init the string array.
	(message_info_new): Set the string array items, as required.
	(message_info_load): And here too.
	(message_info_save): Use accessors to get to strings.
	(message_info_free): Free strings as one.
	(camel_message_info_dup_to): Handle packed array case.
	(camel_folder_summary_add): Use accessors.  And pack the strv
	before storing it.
	(summary_assign_uid): New function to assign a unique uid to a
	message, if it doesn't have one.
	(camel_folder_summary_add): Call assign_uid instead of doing it
	ourselves.
	(camel_folder_summary_info_new_from_parser): "
	(camel_folder_summary_info_new_from_message): "
	(camel_folder_summary_encode_string): constify.
	(camel_folder_summary_encode_token): "
	(summary_build_content_info_message): Fix accessors to messageinfo.
	(CAMEL_FOLDER_SUMMARY_VERSION): Bumped, for removal of
	contentinfo->pos data.
	(camel_folder_summary_info_new_from_parser): Calculate the size
	based on the parser position, not the removed contentinfo stuff.
	(camel_folder_summary_info_new_from_message): Remove size stuff.
	(camel_folder_summary_offset_content): Removed, no longer means anything.
	(content_info_new):
	(content_info_load):
	(content_info_save):
	(summary_build_content_info): Remove stuff for contentinfo->pos*.
	(summary_build_content_info): Take a msginfo argument, set
	attachments flag if we find any attachments.
	(summary_build_content_info_message): set attachments flag if we
	find any attachments.
	(camel_folder_summary_info_new_from_parser): Always scan the
	content info, even if we dont save it.
	(camel_folder_summary_info_new_from_message): And here too.
	(summary_build_content_info): Only create the contentinfo stuff if
	we have it turned on, otherwise just parse and discard.
	(summary_build_content_info_message): Likewise.

svn path=/trunk/; revision=6731
2000-11-30 11:05:36 +00:00

2391 lines
59 KiB
C

/*
* Copyright (C) 2000 Helix Code Inc.
*
* Authors: Michael Zucchi <notzed@helixcode.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <unistd.h>
#include <netinet/in.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "camel-folder-summary.h"
#include <camel/camel-mime-message.h>
#include <camel/camel-multipart.h>
#include <camel/camel-mime-filter.h>
#include <camel/camel-mime-filter-index.h>
#include <camel/camel-mime-filter-charset.h>
#include <camel/camel-mime-filter-save.h>
#include <camel/camel-mime-filter-basic.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-stream-mem.h>
#include "hash-table-utils.h"
#include "e-util/md5-utils.h"
#include "e-util/e-memory.h"
/* this should probably be conditional on it existing */
#define USE_BSEARCH
#define d(x)
#define io(x) /* io debug */
#if 0
extern int strdup_count, malloc_count, free_count;
#endif
#define CAMEL_FOLDER_SUMMARY_VERSION (11)
struct _CamelFolderSummaryPrivate {
GHashTable *filter_charset; /* CamelMimeFilterCharset's indexed by source charset */
CamelMimeFilterIndex *filter_index;
CamelMimeFilterBasic *filter_64;
CamelMimeFilterBasic *filter_qp;
CamelMimeFilterSave *filter_save;
ibex *index;
};
#define _PRIVATE(o) (((CamelFolderSummary *)(o))->priv)
/* trivial lists, just because ... */
struct _node {
struct _node *next;
};
static struct _node *my_list_append(struct _node **list, struct _node *n);
static int my_list_size(struct _node **list);
static int summary_header_load(CamelFolderSummary *, FILE *);
static int summary_header_save(CamelFolderSummary *, FILE *);
static CamelMessageInfo * message_info_new(CamelFolderSummary *, struct _header_raw *);
static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
static CamelMessageInfo * message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg);
static CamelMessageInfo * message_info_load(CamelFolderSummary *, FILE *);
static int message_info_save(CamelFolderSummary *, FILE *, CamelMessageInfo *);
static void message_info_free(CamelFolderSummary *, CamelMessageInfo *);
static CamelMessageContentInfo * content_info_new(CamelFolderSummary *, struct _header_raw *);
static CamelMessageContentInfo * content_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
static CamelMessageContentInfo * content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp);
static CamelMessageContentInfo * content_info_load(CamelFolderSummary *, FILE *);
static int content_info_save(CamelFolderSummary *, FILE *, CamelMessageContentInfo *);
static void content_info_free(CamelFolderSummary *, CamelMessageContentInfo *);
static char *next_uid_string(CamelFolderSummary *s);
static CamelMessageContentInfo * summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp);
static CamelMessageContentInfo * summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object);
static void camel_folder_summary_class_init (CamelFolderSummaryClass *klass);
static void camel_folder_summary_init (CamelFolderSummary *obj);
static void camel_folder_summary_finalize (CamelObject *obj);
static CamelObjectClass *camel_folder_summary_parent;
static void
camel_folder_summary_class_init (CamelFolderSummaryClass *klass)
{
camel_folder_summary_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
klass->summary_header_load = summary_header_load;
klass->summary_header_save = summary_header_save;
klass->message_info_new = message_info_new;
klass->message_info_new_from_parser = message_info_new_from_parser;
klass->message_info_new_from_message = message_info_new_from_message;
klass->message_info_load = message_info_load;
klass->message_info_save = message_info_save;
klass->message_info_free = message_info_free;
klass->content_info_new = content_info_new;
klass->content_info_new_from_parser = content_info_new_from_parser;
klass->content_info_new_from_message = content_info_new_from_message;
klass->content_info_load = content_info_load;
klass->content_info_save = content_info_save;
klass->content_info_free = content_info_free;
klass->next_uid_string = next_uid_string;
}
static void
camel_folder_summary_init (CamelFolderSummary *s)
{
struct _CamelFolderSummaryPrivate *p;
p = _PRIVATE(s) = g_malloc0(sizeof(*p));
p->filter_charset = g_hash_table_new(g_strcase_hash, g_strcase_equal);
s->message_info_size = sizeof(CamelMessageInfo);
s->content_info_size = sizeof(CamelMessageContentInfo);
s->message_info_chunks = NULL;
s->content_info_chunks = NULL;
#ifdef DOESTRV
s->message_info_strings = CAMEL_MESSAGE_INFO_LAST;
#endif
s->version = CAMEL_FOLDER_SUMMARY_VERSION;
s->flags = 0;
s->time = 0;
s->nextuid = 1;
s->messages = g_ptr_array_new();
s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
}
static void free_o_name(void *key, void *value, void *data)
{
camel_object_unref((CamelObject *)value);
g_free(key);
}
static void
camel_folder_summary_finalize (CamelObject *obj)
{
struct _CamelFolderSummaryPrivate *p;
CamelFolderSummary *s = (CamelFolderSummary *)obj;
p = _PRIVATE(obj);
camel_folder_summary_clear(s);
g_ptr_array_free(s->messages, TRUE);
g_hash_table_destroy(s->messages_uid);
g_hash_table_foreach(p->filter_charset, free_o_name, 0);
g_hash_table_destroy(p->filter_charset);
g_free(s->summary_path);
if (s->message_info_chunks)
e_memchunk_destroy(s->message_info_chunks);
if (s->content_info_chunks)
e_memchunk_destroy(s->content_info_chunks);
if (p->filter_index)
camel_object_unref ((CamelObject *)p->filter_index);
if (p->filter_64)
camel_object_unref ((CamelObject *)p->filter_64);
if (p->filter_qp)
camel_object_unref ((CamelObject *)p->filter_qp);
if (p->filter_save)
camel_object_unref ((CamelObject *)p->filter_save);
g_free(p);
}
CamelType
camel_folder_summary_get_type (void)
{
static CamelType type = CAMEL_INVALID_TYPE;
if (type == CAMEL_INVALID_TYPE) {
type = camel_type_register (camel_object_get_type (), "CamelFolderSummary",
sizeof (CamelFolderSummary),
sizeof (CamelFolderSummaryClass),
(CamelObjectClassInitFunc) camel_folder_summary_class_init,
NULL,
(CamelObjectInitFunc) camel_folder_summary_init,
(CamelObjectFinalizeFunc) camel_folder_summary_finalize);
}
return type;
}
/**
* camel_folder_summary_new:
*
* Create a new CamelFolderSummary object.
*
* Return value: A new CamelFolderSummary widget.
**/
CamelFolderSummary *
camel_folder_summary_new (void)
{
CamelFolderSummary *new = CAMEL_FOLDER_SUMMARY ( camel_object_new (camel_folder_summary_get_type ()));
return new;
}
/**
* camel_folder_summary_set_filename:
* @s:
* @name:
*
* Set the filename where the summary will be loaded to/saved from.
**/
void camel_folder_summary_set_filename(CamelFolderSummary *s, const char *name)
{
g_free(s->summary_path);
s->summary_path = g_strdup(name);
}
/**
* camel_folder_summary_set_index:
* @s:
* @index:
*
* 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 *s, ibex *index)
{
struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
p->index = index;
}
/**
* camel_folder_summary_set_build_content:
* @s:
* @state:
*
* 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 *s, gboolean state)
{
s->build_content = state;
}
/**
* camel_folder_summary_count:
* @s:
*
* Get the number of summary items stored in this summary.
*
* Return value: The number of items int he summary.
**/
int
camel_folder_summary_count(CamelFolderSummary *s)
{
return s->messages->len;
}
/**
* camel_folder_summary_index:
* @s:
* @i:
*
* Retrieve a summary item by index number.
*
* Return value: The summary item, or NULL if the index @i is out
* of range.
**/
CamelMessageInfo *
camel_folder_summary_index(CamelFolderSummary *s, int i)
{
if (i<s->messages->len)
return g_ptr_array_index(s->messages, i);
return NULL;
}
/**
* camel_folder_summary_uid:
* @s:
* @uid:
*
* Retrieve a summary item by uid.
*
* Return value: The summary item, or NULL if the uid @uid
* is not available.
**/
CamelMessageInfo *
camel_folder_summary_uid(CamelFolderSummary *s, const char *uid)
{
return g_hash_table_lookup(s->messages_uid, uid);
}
/**
* camel_folder_summary_next_uid:
* @s:
*
* Generate a new unique uid value as an integer. This
* may be used to create a unique sequence of numbers.
*
* Return value: The next unique uid value.
**/
guint32 camel_folder_summary_next_uid(CamelFolderSummary *s)
{
guint32 uid = s->nextuid++;
/* FIXME: sync this to disk */
/* summary_header_save(s);*/
return uid;
}
/**
* camel_folder_summary_set_uid:
* @s:
* @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_uid(CamelFolderSummary *s, guint32 uid)
{
/* TODO: sync to disk? */
s->nextuid = MAX(s->nextuid, uid);
}
/**
* camel_folder_summary_next_uid_string:
* @s:
*
* Retrieve the next uid, but as a formatted string.
*
* Return value: The next uid as an unsigned integer string.
* This string must be freed by the caller.
**/
char *
camel_folder_summary_next_uid_string(CamelFolderSummary *s)
{
return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->next_uid_string(s);
}
/* loads the content descriptions, recursively */
static CamelMessageContentInfo *
perform_content_info_load(CamelFolderSummary *s, FILE *in)
{
int i;
guint32 count;
CamelMessageContentInfo *ci, *part;
ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_load(s, in);
camel_folder_summary_decode_uint32(in, &count);
for (i=0;i<count;i++) {
part = perform_content_info_load(s, in);
if (part) {
my_list_append((struct _node **)&ci->childs, (struct _node *)part);
part->parent = ci;
} else {
g_warning("Summary file format messed up?");
}
}
return ci;
}
int
camel_folder_summary_load(CamelFolderSummary *s)
{
FILE *in;
int i;
CamelMessageInfo *mi;
g_assert(s->summary_path);
in = fopen(s->summary_path, "r");
if ( in == NULL ) {
return -1;
}
if ( ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in) == -1) {
fclose(in);
return -1;
}
/* now read in each message ... */
/* FIXME: check returns */
for (i=0;i<s->saved_count;i++) {
mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_load(s, in);
if (s->build_content) {
mi->content = perform_content_info_load(s, in);
}
camel_folder_summary_add(s, mi);
}
if (fclose(in) == -1)
return -1;
s->flags &= ~CAMEL_SUMMARY_DIRTY;
return 0;
}
/* saves the content descriptions, recursively */
static int
perform_content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
{
CamelMessageContentInfo *part;
((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_save(s, out, ci);
camel_folder_summary_encode_uint32(out, my_list_size((struct _node **)&ci->childs));
part = ci->childs;
while (part) {
perform_content_info_save(s, out, part);
part = part->next;
}
return 0;
}
/**
* camel_folder_summary_save:
* @s:
*
* Writes the summary to disk. The summary is only written if changes
* have occured.
*
* Return value: Returns -1 on error.
**/
int
camel_folder_summary_save(CamelFolderSummary *s)
{
FILE *out;
int fd;
int i;
guint32 count;
CamelMessageInfo *mi;
g_assert(s->summary_path);
if ((s->flags & CAMEL_SUMMARY_DIRTY) == 0)
return 0;
fd = open(s->summary_path, O_RDWR|O_CREAT, 0600);
if (fd == -1)
return -1;
out = fdopen(fd, "w");
if ( out == NULL ) {
close(fd);
return -1;
}
io(printf("saving header\n"));
if ( ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_save(s, out) == -1) {
fclose(out);
return -1;
}
/* now write out each message ... */
/* FIXME: check returns */
count = camel_folder_summary_count(s);
for (i=0;i<count;i++) {
mi = camel_folder_summary_index(s, i);
((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_save(s, out, mi);
if (s->build_content) {
perform_content_info_save(s, out, mi->content);
}
}
if (fclose(out) == -1)
return -1;
s->flags &= ~CAMEL_SUMMARY_DIRTY;
return 0;
}
static void
summary_assign_uid(CamelFolderSummary *s, CamelMessageInfo *info)
{
const char *uid;
uid = camel_message_info_uid(info);
if (uid == NULL || uid[0] == 0) {
camel_message_info_set_uid(info, camel_folder_summary_next_uid_string(s));
uid = camel_message_info_uid(info);
}
while (g_hash_table_lookup(s->messages_uid, uid)) {
g_warning("Trying to insert message with clashing uid (%s). new uid re-assigned", camel_message_info_uid(info));
camel_message_info_set_uid(info, camel_folder_summary_next_uid_string(s));
uid = camel_message_info_uid(info);
info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
}
}
/**
* camel_folder_summary_add:
* @s:
* @info:
*
* Adds a new @info record to the summary. If @info->uid is NULL, then a new
* uid is automatically re-assigned by calling :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 *s, CamelMessageInfo *info)
{
if (info == NULL)
return;
summary_assign_uid(s, info);
#ifdef DOESTRV
/* this is vitally important, and also if this is ever modified, then
the hash table needs to be resynced */
info->strings = e_strv_pack(info->strings);
#endif
g_ptr_array_add(s->messages, info);
g_hash_table_insert(s->messages_uid, (char *)camel_message_info_uid(info), info);
s->flags |= CAMEL_SUMMARY_DIRTY;
}
/**
* camel_folder_summary_add_from_header:
* @s:
* @h:
*
* Build a new info record based on a set of headers, and add it to the
* summary.
*
* Note that this function should not be used if build_content_info has
* been specified for this summary.
*
* Return value: The newly added record.
**/
CamelMessageInfo *camel_folder_summary_add_from_header(CamelFolderSummary *s, struct _header_raw *h)
{
CamelMessageInfo *info = camel_folder_summary_info_new_from_header(s, h);
camel_folder_summary_add(s, info);
return info;
}
/**
* camel_folder_summary_add_from_parser:
* @s:
* @mp:
*
* Build a new info record based on the current position of a CamelMimeParser.
*
* The parser should be positioned before the start of the message to summarise.
* This function may be used if build_contnet_info or an index has been
* specified for the summary.
*
* Return value: The newly added record.
**/
CamelMessageInfo *camel_folder_summary_add_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
CamelMessageInfo *info = camel_folder_summary_info_new_from_parser(s, mp);
camel_folder_summary_add(s, info);
return info;
}
/**
* camel_folder_summary_add_from_message:
* @s:
* @msg:
*
* Add a summary item from an existing message.
*
* Return value:
**/
CamelMessageInfo *camel_folder_summary_add_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
CamelMessageInfo *info = camel_folder_summary_info_new_from_message(s, msg);
camel_folder_summary_add(s, info);
return info;
}
/**
* camel_folder_summary_info_new_from_header:
* @s:
* @h:
*
* Create a new info record from a header.
*
* Return value: Guess? This info record MUST be freed using
* camel_folder_summary_info_free(), camel_message_info_free() will not work.
**/
CamelMessageInfo *camel_folder_summary_info_new_from_header(CamelFolderSummary *s, struct _header_raw *h)
{
return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s))) -> message_info_new(s, h);
}
/**
* camel_folder_summary_info_new_from_parser:
* @s:
* @mp:
*
* Create a new info record from a parser. If the parser cannot
* determine a uid, then a new 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.
*
* Return value: Guess? This info record MUST be freed using
* camel_folder_summary_info_free(), camel_message_info_free() will not work.
**/
CamelMessageInfo *camel_folder_summary_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
CamelMessageInfo *info = NULL;
char *buffer;
int len;
struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
off_t start;
/* 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) != HSCAN_EOF) {
info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_parser(s, 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 */
summary_assign_uid(s, info);
if (p->index) {
if (p->filter_index == NULL)
p->filter_index = camel_mime_filter_index_new_ibex(p->index);
camel_mime_filter_index_set_name(p->filter_index, (char *)camel_message_info_uid(info));
ibex_unindex(p->index, (char *)camel_message_info_uid(info));
}
/* always scan the content info, even if we dont save it */
info->content = summary_build_content_info(s, info, mp);
info->size = camel_mime_parser_tell(mp) - start;
}
return info;
}
/**
* camel_folder_summary_info_new_from_message:
* @:
* @:
*
* Create a summary item from a message.
*
* Return value:
**/
CamelMessageInfo *camel_folder_summary_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
CamelMessageInfo *info;
struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_message(s, msg);
/* assign a unique uid, this is slightly 'wrong' as we do not really
* know if we are going to store this in the summary, but no matter */
summary_assign_uid(s, info);
if (p->index)
ibex_unindex(p->index, (char *)camel_message_info_uid(info));
info->content = summary_build_content_info_message(s, info, (CamelMimePart *)msg);
/* FIXME: calculate the size as part of build_content_info_message */
/* info->size = ... */
return info;
}
static void
perform_content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
{
CamelMessageContentInfo *pw, *pn;
pw = ci->childs;
((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_free(s, ci);
while (pw) {
pn = pw->next;
perform_content_info_free(s, pw);
pw = pn;
}
}
/**
* camel_folder_summary_info_free:
* @s:
* @mi:
*
* Free the message info @mi, and all associated memory.
**/
void camel_folder_summary_info_free(CamelFolderSummary *s, CamelMessageInfo *mi)
{
CamelMessageContentInfo *ci;
g_assert(mi);
g_assert(s);
ci = mi->content;
((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_free(s, mi);
if (s->build_content && ci) {
perform_content_info_free(s, ci);
}
}
/**
* camel_folder_summary_touch:
* @s:
*
* Mark the summary as changed, so that a save will save it.
**/
void
camel_folder_summary_touch(CamelFolderSummary *s)
{
s->flags |= CAMEL_SUMMARY_DIRTY;
}
/**
* camel_folder_summary_clear:
* @s:
*
* Empty the summary contents.
**/
void
camel_folder_summary_clear(CamelFolderSummary *s)
{
int i;
if (camel_folder_summary_count(s) == 0)
return;
for (i=0;i<camel_folder_summary_count(s);i++)
camel_folder_summary_info_free(s, camel_folder_summary_index(s, i));
g_ptr_array_set_size(s->messages, 0);
g_hash_table_destroy(s->messages_uid);
s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
s->flags |= CAMEL_SUMMARY_DIRTY;
}
/**
* camel_folder_summary_remove:
* @s:
* @info:
*
* Remove a specific @info record from the summary.
**/
void camel_folder_summary_remove(CamelFolderSummary *s, CamelMessageInfo *info)
{
g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
g_ptr_array_remove(s->messages, info);
camel_folder_summary_info_free(s, info);
s->flags |= CAMEL_SUMMARY_DIRTY;
}
/**
* camel_folder_summary_remove_uid:
* @s:
* @uid:
*
* Remove a specific info record from the summary, by @uid.
**/
void camel_folder_summary_remove_uid(CamelFolderSummary *s, const char *uid)
{
CamelMessageInfo *oldinfo;
char *olduid;
if (g_hash_table_lookup_extended(s->messages_uid, uid, (void *)&olduid, (void *)&oldinfo)) {
camel_folder_summary_remove(s, oldinfo);
g_free(olduid);
}
}
/**
* camel_folder_summary_remove_index:
* @s:
* @index:
*
* Remove a specific info record from the summary, by index.
**/
void camel_folder_summary_remove_index(CamelFolderSummary *s, int index)
{
CamelMessageInfo *oldinfo;
oldinfo = camel_folder_summary_index (s, index);
if (oldinfo)
camel_folder_summary_remove(s, oldinfo);
}
/**
* camel_folder_summary_encode_uint32:
* @out:
* @value:
*
* Utility function to save an uint32 to a file.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_encode_uint32(FILE *out, guint32 value)
{
int i;
io(printf("Encoding int %u\n", value));
for (i=28;i>0;i-=7) {
if (value >= (1<<i)) {
unsigned int c = (value>>i) & 0x7f;
if (fputc(c, out) == -1)
return -1;
}
}
return fputc(value | 0x80, out);
}
/**
* camel_folder_summary_decode_uint32:
* @in:
* @dest:
*
* Retrieve an encoded uint32 from a file.
*
* Return value: -1 on error. @*dest will contain the
* decoded value.
**/
int
camel_folder_summary_decode_uint32(FILE *in, guint32 *dest)
{
guint32 value=0, v;
/* until we get the last byte, keep decoding 7 bits at a time */
while ( ((v = fgetc(in)) & 0x80) == 0 && v!=EOF) {
value |= v;
value <<= 7;
}
if (v == EOF) {
*dest = value>>7;
return -1;
}
*dest = value | (v&0x7f);
io(printf("Decoding int %u\n", *dest));
return 0;
}
/**
* camel_folder_summary_encode_fixed_int32:
* @out:
* @value:
*
* Encode a gint32, performing no compression, but converting
* to network order.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_encode_fixed_int32(FILE *out, gint32 value)
{
guint32 save;
save = htonl(value);
if (fwrite(&save, sizeof(save), 1, out) != 1)
return -1;
return 0;
}
/**
* camel_folder_summary_decode_fixed_int32:
* @in:
* @dest:
*
* Retrieve a gint32.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_decode_fixed_int32(FILE *in, gint32 *dest)
{
guint32 save;
if (fread(&save, sizeof(save), 1, in) == 1) {
*dest = ntohl(save);
return 0;
} else {
return -1;
}
}
/**
* camel_folder_summary_encode_time_t:
* @out:
* @value:
*
* Encode a time_t value to the file.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_encode_time_t(FILE *out, time_t value)
{
int i;
for (i=sizeof(time_t)-1;i>=0;i--) {
if (fputc((value >> (i*8)) & 0xff, out) == -1)
return -1;
}
return 0;
}
/**
* camel_folder_summary_decode_time_t:
* @in:
* @dest:
*
* Decode a time_t value.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_decode_time_t(FILE *in, time_t *dest)
{
time_t save = 0;
unsigned int v;
int i = sizeof(time_t) - 1;
while ( i>=0 && (v = fgetc(in)) != EOF) {
save |= v << (i*8);
i--;
}
*dest = save;
if (v == EOF)
return -1;
return 0;
}
/**
* camel_folder_summary_encode_off_t:
* @out:
* @value:
*
* Encode an off_t type.
*
* Return value:
**/
int
camel_folder_summary_encode_off_t(FILE *out, off_t value)
{
int i;
for (i=sizeof(off_t)-1;i>=0;i--) {
if (fputc((value >> (i*8)) & 0xff, out) == -1)
return -1;
}
return 0;
}
/**
* camel_folder_summary_decode_off_t:
* @in:
* @dest:
*
* Decode an off_t type.
*
* Return value:
**/
int
camel_folder_summary_decode_off_t(FILE *in, off_t *dest)
{
off_t save = 0;
unsigned int v;
int i = sizeof(off_t) - 1;
while ( i>=0 && (v = fgetc(in)) != EOF) {
save |= v << (i*8);
i--;
}
*dest = save;
if (v == EOF)
return -1;
return 0;
}
/* should be sorted, for binary search */
/* This is a tokenisation mechanism for strings written to the
summary - to save space.
This list can have at most 31 words. */
static char * tokens[] = {
"7bit",
"8bit",
"alternative",
"application",
"base64",
"boundary",
"charset",
"filename",
"html",
"image",
"iso-8859-1",
"iso-8859-8",
"message",
"mixed",
"multipart",
"name",
"octet-stream",
"parallel",
"plain",
"postscript",
"quoted-printable",
"related",
"rfc822",
"text",
"us-ascii", /* 25 words */
};
#define tokens_len (sizeof(tokens)/sizeof(tokens[0]))
/* baiscally ...
0 = null
1-tokens_len == tokens[id-1]
>=32 string, length = n-32
*/
#ifdef USE_BSEARCH
static int
token_search_cmp(char *key, char **index)
{
d(printf("comparing '%s' to '%s'\n", key, *index));
return strcmp(key, *index);
}
#endif
/**
* camel_folder_summary_encode_token:
* @out:
* @str:
*
* Encode a string value, but use tokenisation and compression
* to reduce the size taken for common mailer words. This
* can still be used to encode normal strings as well.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_encode_token(FILE *out, const char *str)
{
io(printf("Encoding token: '%s'\n", str));
if (str == NULL) {
return camel_folder_summary_encode_uint32(out, 0);
} else {
int len = strlen(str);
int i, token=-1;
if (len <= 16) {
char lower[32];
char **match;
for (i=0;i<len;i++)
lower[i] = tolower(str[i]);
lower[i] = 0;
#ifdef USE_BSEARCH
match = bsearch(lower, tokens, tokens_len, sizeof(char *), (int (*)(const void *, const void *))token_search_cmp);
if (match)
token = match-tokens;
#else
for (i=0;i<tokens_len;i++) {
if (!strcmp(tokens[i], lower)) {
token = i;
break;
}
}
#endif
}
if (token != -1) {
return camel_folder_summary_encode_uint32(out, token+1);
} else {
if (camel_folder_summary_encode_uint32(out, len+32) == -1)
return -1;
if (fwrite(str, len, 1, out) != 1)
return -1;
}
}
return 0;
}
/**
* camel_folder_summary_decode_token:
* @in:
* @str:
*
* Decode a token value.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_decode_token(FILE *in, char **str)
{
char *ret;
guint32 len;
io(printf("Decode token ...\n"));
if (camel_folder_summary_decode_uint32(in, &len) == -1) {
g_warning("Could not decode token from file");
*str = NULL;
return -1;
}
if (len<32) {
if (len <= 0) {
ret = NULL;
} else if (len<= tokens_len) {
ret = g_strdup(tokens[len-1]);
} else {
g_warning("Invalid token encountered: %d", len);
*str = NULL;
return -1;
}
} else if (len > 10240) {
g_warning("Got broken string header length: %d bytes", len);
*str = NULL;
return -1;
} else {
len -= 32;
ret = g_malloc(len+1);
if (fread(ret, len, 1, in) != 1) {
g_free(ret);
*str = NULL;
return -1;
}
ret[len]=0;
}
io(printf("Token = '%s'\n", ret));
*str = ret;
return 0;
}
/**
* camel_folder_summary_encode_string:
* @out:
* @str:
*
* Encode a normal string and save it in the output file.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_encode_string(FILE *out, const char *str)
{
register int len;
io(printf("Encoding string: '%s'\n", str));
if (str == NULL)
return camel_folder_summary_encode_uint32(out, 0);
len = strlen(str);
if (camel_folder_summary_encode_uint32(out, len+1) == -1)
return -1;
if (fwrite(str, len, 1, out) == 1)
return 0;
return -1;
}
/**
* camel_folder_summary_decode_string:
* @in:
* @str:
*
* Decode a normal string from the input file.
*
* Return value: -1 on error.
**/
int
camel_folder_summary_decode_string(FILE *in, char **str)
{
gint32 len;
register char *ret;
io(printf("Decode string ...\n", str));
if (camel_folder_summary_decode_uint32(in, &len) == -1) {
*str = NULL;
return -1;
}
len--;
if (len < 0) {
*str = NULL;
io(printf("String = '%s'\n", *str));
return -1;
}
ret = g_malloc(len+1);
if (fread(ret, len, 1, in) != 1) {
g_free(ret);
*str = NULL;
return -1;
}
io(printf("String = '%s'\n", ret));
ret[len] = 0;
*str = ret;
return 0;
}
static struct _node *
my_list_append(struct _node **list, struct _node *n)
{
struct _node *ln = (struct _node *)list;
while (ln->next)
ln = ln->next;
n->next = 0;
ln->next = n;
return n;
}
static int
my_list_size(struct _node **list)
{
int len = 0;
struct _node *ln = (struct _node *)list;
while (ln->next) {
ln = ln->next;
len++;
}
return len;
}
static int
summary_header_load(CamelFolderSummary *s, FILE *in)
{
gint32 version, flags, nextuid, count;
time_t time;
fseek(in, 0, SEEK_SET);
io(printf("Loading header\n"));
if (camel_folder_summary_decode_fixed_int32(in, &version) == -1
|| camel_folder_summary_decode_fixed_int32(in, &flags) == -1
|| camel_folder_summary_decode_fixed_int32(in, &nextuid) == -1
|| camel_folder_summary_decode_time_t(in, &time) == -1
|| camel_folder_summary_decode_fixed_int32(in, &count) == -1) {
return -1;
}
s->nextuid = nextuid;
s->flags = flags;
s->time = time;
s->saved_count = count;
if (s->version != version) {
g_warning("Summary header version mismatch");
return -1;
}
return 0;
}
static int
summary_header_save(CamelFolderSummary *s, FILE *out)
{
fseek(out, 0, SEEK_SET);
io(printf("Savining header\n"));
camel_folder_summary_encode_fixed_int32(out, s->version);
camel_folder_summary_encode_fixed_int32(out, s->flags);
camel_folder_summary_encode_fixed_int32(out, s->nextuid);
camel_folder_summary_encode_time_t(out, s->time);
return camel_folder_summary_encode_fixed_int32(out, camel_folder_summary_count(s));
}
/* are these even useful for anything??? */
static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
CamelMessageInfo *mi = NULL;
int state;
state = camel_mime_parser_state(mp);
switch (state) {
case HSCAN_HEADER:
case HSCAN_MESSAGE:
case HSCAN_MULTIPART:
mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new(s, camel_mime_parser_headers_raw(mp));
break;
default:
g_error("Invalid parser state");
}
return mi;
}
static CamelMessageContentInfo * content_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
CamelMessageContentInfo *ci = NULL;
switch (camel_mime_parser_state(mp)) {
case HSCAN_HEADER:
case HSCAN_MESSAGE:
case HSCAN_MULTIPART:
ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new(s, camel_mime_parser_headers_raw(mp));
if (ci) {
ci->type = camel_mime_parser_content_type(mp);
header_content_type_ref(ci->type);
}
break;
default:
g_error("Invalid parser state");
}
return ci;
}
static CamelMessageInfo * message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
CamelMessageInfo *mi;
mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new(s, ((CamelMimePart *)msg)->headers);
return mi;
}
static CamelMessageContentInfo * content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp)
{
CamelMessageContentInfo *ci;
ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new(s, mp->headers);
return ci;
}
#warning "These should be made private again, easy to fix (used in filter-driver)"
char *
camel_folder_summary_format_address(struct _header_raw *h, const char *name)
{
struct _header_address *addr;
const char *text;
char *ret;
text = header_raw_find (&h, name, NULL);
addr = header_address_decode (text);
if (addr) {
ret = header_address_list_format (addr);
header_address_list_clear (&addr);
} else {
ret = g_strdup (text);
}
return ret;
}
char *
camel_folder_summary_format_string(struct _header_raw *h, const char *name)
{
const char *text;
text = header_raw_find(&h, name, NULL);
if (text) {
while (isspace(*text))
text++;
return header_decode_string(text);
} else {
return NULL;
}
}
/**
* camel_folder_summary_info_new:
* @s:
*
* Allocate a new camel message info, suitable for adding
* to this summary.
*
* Return value:
**/
CamelMessageInfo *
camel_folder_summary_info_new(CamelFolderSummary *s)
{
CamelMessageInfo *mi;
if (s->message_info_chunks == NULL)
s->message_info_chunks = e_memchunk_new(32, s->message_info_size);
mi = e_memchunk_alloc(s->message_info_chunks);
memset(mi, 0, s->message_info_size);
#ifdef DOESTRV
mi->strings = e_strv_new(s->message_info_strings);
#endif
return mi;
}
static CamelMessageContentInfo *
content_info_alloc(CamelFolderSummary *s)
{
CamelMessageContentInfo *ci;
if (s->content_info_chunks == NULL)
s->content_info_chunks = e_memchunk_new(32, s->content_info_size);
ci = e_memchunk_alloc(s->content_info_chunks);
memset(ci, 0, s->content_info_size);
return ci;
}
static CamelMessageInfo *
message_info_new(CamelFolderSummary *s, struct _header_raw *h)
{
CamelMessageInfo *mi;
const char *received;
guchar digest[16];
struct _header_references *refs, *scan;
char *msgid;
int count;
mi = camel_folder_summary_info_new(s);
#ifdef DOESTRV
msgid = camel_folder_summary_format_string(h, "subject");
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, msgid);
msgid = camel_folder_summary_format_address(h, "from");
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_FROM, msgid);
msgid = camel_folder_summary_format_address(h, "to");
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_TO, msgid);
msgid = camel_folder_summary_format_address(h, "cc");
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_CC, msgid);
#else
mi->subject = camel_folder_summary_format_string(h, "subject");
mi->from = camel_folder_summary_format_address(h, "from");
mi->to = camel_folder_summary_format_address(h, "to");
mi->cc = camel_folder_summary_format_address(h, "cc");
#endif
mi->user_flags = NULL;
mi->user_tags = NULL;
mi->date_sent = header_decode_date(header_raw_find(&h, "date", NULL), NULL);
received = header_raw_find(&h, "received", NULL);
if (received)
received = strrchr(received, ';');
if (received)
mi->date_received = header_decode_date(received + 1, NULL);
else
mi->date_received = 0;
msgid = header_msgid_decode(header_raw_find(&h, "message-id", NULL));
if (msgid) {
md5_get_digest(msgid, strlen(msgid), digest);
memcpy(mi->message_id.id.hash, digest, sizeof(mi->message_id.id.hash));
g_free(msgid);
}
/* if we have a references, use that, otherwise, see if we have an in-reply-to
header, with parsable content, otherwise *shrug* */
if ((refs = header_references_decode(header_raw_find(&h, "references", NULL))) != NULL
|| (refs = header_references_decode(header_raw_find(&h, "in-reply-to", NULL))) != NULL) {
count = 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) {
/* FIXME: the id might be NULL because of a small bug in camel-mime-utils */
if (scan->id) {
md5_get_digest(scan->id, strlen(scan->id), digest);
memcpy(mi->references->references[count].id.hash, digest, sizeof(mi->message_id.id.hash));
count++;
}
scan = scan->next;
}
mi->references->size = count;
header_references_list_clear(&refs);
}
return mi;
}
static CamelMessageInfo *
message_info_load(CamelFolderSummary *s, FILE *in)
{
CamelMessageInfo *mi;
guint count;
int i;
#ifdef DOESTRV
char *tmp;
#endif
mi = camel_folder_summary_info_new(s);
io(printf("Loading message info\n"));
#ifdef DOESTRV
camel_folder_summary_decode_string(in, &tmp);
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_UID, tmp);
camel_folder_summary_decode_uint32(in, &mi->flags);
camel_folder_summary_decode_uint32(in, &mi->size);
camel_folder_summary_decode_time_t(in, &mi->date_sent);
camel_folder_summary_decode_time_t(in, &mi->date_received);
camel_folder_summary_decode_string(in, &tmp);
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, tmp);
camel_folder_summary_decode_string(in, &tmp);
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_FROM, tmp);
camel_folder_summary_decode_string(in, &tmp);
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_TO, tmp);
camel_folder_summary_decode_string(in, &tmp);
e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_CC, tmp);
#else
camel_folder_summary_decode_string(in, &mi->uid);
camel_folder_summary_decode_uint32(in, &mi->flags);
camel_folder_summary_decode_uint32(in, &mi->size);
camel_folder_summary_decode_time_t(in, &mi->date_sent);
camel_folder_summary_decode_time_t(in, &mi->date_received);
camel_folder_summary_decode_string(in, &mi->subject);
camel_folder_summary_decode_string(in, &mi->from);
camel_folder_summary_decode_string(in, &mi->to);
camel_folder_summary_decode_string(in, &mi->cc);
#endif
mi->content = NULL;
camel_folder_summary_decode_fixed_int32(in, &mi->message_id.id.part.hi);
camel_folder_summary_decode_fixed_int32(in, &mi->message_id.id.part.lo);
camel_folder_summary_decode_uint32(in, &count);
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++) {
camel_folder_summary_decode_fixed_int32(in, &mi->references->references[i].id.part.hi);
camel_folder_summary_decode_fixed_int32(in, &mi->references->references[i].id.part.lo);
}
}
camel_folder_summary_decode_uint32(in, &count);
for (i=0;i<count;i++) {
char *name;
camel_folder_summary_decode_string(in, &name);
camel_flag_set(&mi->user_flags, name, TRUE);
g_free(name);
}
camel_folder_summary_decode_uint32(in, &count);
for (i=0;i<count;i++) {
char *name, *value;
camel_folder_summary_decode_string(in, &name);
camel_folder_summary_decode_string(in, &value);
camel_tag_set(&mi->user_tags, name, value);
g_free(name);
g_free(value);
}
return mi;
}
static int
message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *mi)
{
guint32 count;
CamelFlag *flag;
CamelTag *tag;
int i;
io(printf("Saving message info\n"));
camel_folder_summary_encode_string(out, camel_message_info_uid(mi));
camel_folder_summary_encode_uint32(out, mi->flags);
camel_folder_summary_encode_uint32(out, mi->size);
camel_folder_summary_encode_time_t(out, mi->date_sent);
camel_folder_summary_encode_time_t(out, mi->date_received);
camel_folder_summary_encode_string(out, camel_message_info_subject(mi));
camel_folder_summary_encode_string(out, camel_message_info_from(mi));
camel_folder_summary_encode_string(out, camel_message_info_to(mi));
camel_folder_summary_encode_string(out, camel_message_info_cc(mi));
camel_folder_summary_encode_fixed_int32(out, mi->message_id.id.part.hi);
camel_folder_summary_encode_fixed_int32(out, mi->message_id.id.part.lo);
if (mi->references) {
camel_folder_summary_encode_uint32(out, mi->references->size);
for (i=0;i<mi->references->size;i++) {
camel_folder_summary_encode_fixed_int32(out, mi->references->references[i].id.part.hi);
camel_folder_summary_encode_fixed_int32(out, mi->references->references[i].id.part.lo);
}
} else {
camel_folder_summary_encode_uint32(out, 0);
}
count = camel_flag_list_size(&mi->user_flags);
camel_folder_summary_encode_uint32(out, count);
flag = mi->user_flags;
while (flag) {
camel_folder_summary_encode_string(out, flag->name);
flag = flag->next;
}
count = camel_tag_list_size(&mi->user_tags);
camel_folder_summary_encode_uint32(out, count);
tag = mi->user_tags;
while (tag) {
camel_folder_summary_encode_string(out, tag->name);
camel_folder_summary_encode_string(out, tag->value);
tag = tag->next;
}
return ferror(out);
}
static void
message_info_free(CamelFolderSummary *s, CamelMessageInfo *mi)
{
#ifdef DOESTRV
e_strv_destroy(mi->strings);
#else
g_free(mi->uid);
g_free(mi->subject);
g_free(mi->from);
g_free(mi->to);
g_free(mi->cc);
#endif
g_free(mi->references);
camel_flag_list_free(&mi->user_flags);
camel_tag_list_free(&mi->user_tags);
e_memchunk_free(s->message_info_chunks, mi);
}
static CamelMessageContentInfo *
content_info_new(CamelFolderSummary *s, struct _header_raw *h)
{
CamelMessageContentInfo *ci;
ci = content_info_alloc(s);
ci->id = header_msgid_decode(header_raw_find(&h, "content-id", NULL));
ci->description = header_decode_string(header_raw_find(&h, "content-description", NULL));
ci->encoding = header_content_encoding_decode(header_raw_find(&h, "content-transfer-encoding", NULL));
return ci;
}
static CamelMessageContentInfo *
content_info_load(CamelFolderSummary *s, FILE *in)
{
CamelMessageContentInfo *ci;
char *type, *subtype;
guint32 count, i;
struct _header_content_type *ct;
io(printf("Loading content info\n"));
ci = content_info_alloc(s);
camel_folder_summary_decode_token(in, &type);
camel_folder_summary_decode_token(in, &subtype);
ct = header_content_type_new(type, subtype);
g_free(type); /* can this be removed? */
g_free(subtype);
camel_folder_summary_decode_uint32(in, &count);
for (i=0;i<count;i++) {
char *name, *value;
camel_folder_summary_decode_token(in, &name);
camel_folder_summary_decode_token(in, &value);
header_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;
camel_folder_summary_decode_token(in, &ci->id);
camel_folder_summary_decode_token(in, &ci->description);
camel_folder_summary_decode_token(in, &ci->encoding);
ci->childs = NULL;
return ci;
}
static int
content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
{
struct _header_content_type *ct;
struct _header_param *hp;
io(printf("Saving content info\n"));
ct = ci->type;
if (ct) {
camel_folder_summary_encode_token(out, ct->type);
camel_folder_summary_encode_token(out, ct->subtype);
camel_folder_summary_encode_uint32(out, my_list_size((struct _node **)&ct->params));
hp = ct->params;
while (hp) {
camel_folder_summary_encode_token(out, hp->name);
camel_folder_summary_encode_token(out, hp->value);
hp = hp->next;
}
} else {
camel_folder_summary_encode_token(out, NULL);
camel_folder_summary_encode_token(out, NULL);
camel_folder_summary_encode_uint32(out, 0);
}
camel_folder_summary_encode_token(out, ci->id);
camel_folder_summary_encode_token(out, ci->description);
return camel_folder_summary_encode_token(out, ci->encoding);
}
static void
content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
{
header_content_type_unref(ci->type);
g_free(ci->id);
g_free(ci->description);
g_free(ci->encoding);
e_memchunk_free(s->content_info_chunks, ci);
}
static char *
next_uid_string(CamelFolderSummary *s)
{
return g_strdup_printf("%u", camel_folder_summary_next_uid(s));
}
/*
OK
Now this is where all the "smarts" happen, where the content info is built,
and any indexing and what not is performed
*/
static CamelMessageContentInfo *
summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp)
{
int state, len;
char *buffer;
CamelMessageContentInfo *info = NULL;
struct _header_content_type *ct;
int body;
int enc_id = -1, chr_id = -1, idx_id = -1;
struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
CamelMimeFilterCharset *mfc;
CamelMessageContentInfo *part;
d(printf("building content info\n"));
/* start of this part */
state = camel_mime_parser_step(mp, &buffer, &len);
body = camel_mime_parser_tell(mp);
if (s->build_content)
info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_parser(s, mp);
switch(state) {
case HSCAN_HEADER:
/* check content type for indexing, then read body */
ct = camel_mime_parser_content_type(mp);
/* update attachments flag as we go */
if (!header_content_type_is(ct, "text", "*"))
msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
if (p->index && header_content_type_is(ct, "text", "*")) {
char *encoding;
const char *charset;
d(printf("generating index:\n"));
encoding = header_content_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL));
if (encoding) {
if (!strcasecmp(encoding, "base64")) {
d(printf(" decoding base64\n"));
if (p->filter_64 == NULL)
p->filter_64 = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_64);
} else if (!strcasecmp(encoding, "quoted-printable")) {
d(printf(" decoding quoted-printable\n"));
if (p->filter_qp == NULL)
p->filter_qp = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC);
enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_qp);
} else {
d(printf(" ignoring encoding %s\n", encoding));
}
g_free(encoding);
}
charset = header_content_type_param(ct, "charset");
if (charset!=NULL
&& !(strcasecmp(charset, "us-ascii")==0
|| 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_convert(charset, "UTF-8");
if (mfc)
g_hash_table_insert(p->filter_charset, g_strdup(charset), mfc);
}
if (mfc) {
chr_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfc);
} else {
g_warning("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset);
}
}
/* and this filter actually does the indexing */
idx_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_index);
}
/* and scan/index everything */
while (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_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, idx_id);
break;
case HSCAN_MULTIPART:
d(printf("Summarising multipart\n"));
/* update attachments flag as we go */
ct = camel_mime_parser_content_type(mp);
if (header_content_type_is(ct, "multipart", "mixed"))
msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
while (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_MULTIPART_END) {
camel_mime_parser_unstep(mp);
part = summary_build_content_info(s, msginfo, mp);
if (part) {
part->parent = info;
my_list_append((struct _node **)&info->childs, (struct _node *)part);
}
}
break;
case HSCAN_MESSAGE:
d(printf("Summarising message\n"));
/* update attachments flag as we go */
msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
part = summary_build_content_info(s, 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 != HSCAN_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 */
static CamelMessageContentInfo *
summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object)
{
CamelDataWrapper *containee;
int parts, i;
struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
CamelMessageContentInfo *info = NULL, *child;
if (s->build_content)
info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_message(s, object);
containee = camel_medium_get_content_object(CAMEL_MEDIUM(object));
if (containee == NULL)
return info;
/* TODO: I find it odd that get_part and get_content_object do not
add a reference, probably need fixing for multithreading */
/* check for attachments */
if (gmime_content_field_is_type(CAMEL_DATA_WRAPPER(containee)->mime_type, "multipart", "*")) {
if (gmime_content_field_is_type(CAMEL_DATA_WRAPPER(containee)->mime_type, "multipart", "mixed"))
msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
} else if (!gmime_content_field_is_type(CAMEL_DATA_WRAPPER(containee)->mime_type, "text", "*"))
msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
/* 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_assert(part);
child = summary_build_content_info_message(s, 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(s, msginfo, (CamelMimePart *)containee);
if (child) {
child->parent = info;
my_list_append((struct _node **)&info->childs, (struct _node *)child);
}
} else if (p->index
&& gmime_content_field_is_type(CAMEL_DATA_WRAPPER(containee)->mime_type, "text", "*")) {
/* index all text parts if we're indexing */
CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new();
camel_data_wrapper_write_to_stream(containee, (CamelStream *)mem);
ibex_index_buffer(p->index, (char *)camel_message_info_uid(msginfo), mem->buffer->data, mem->buffer->len, NULL);
camel_object_unref((CamelObject *)mem);
}
return info;
}
/**
* camel_flag_get:
* @list:
* @name:
*
* Find the state of the flag @name in @list.
*
* Return value: The state of the flag (TRUE or FALSE).
**/
gboolean
camel_flag_get(CamelFlag **list, const char *name)
{
CamelFlag *flag;
flag = *list;
while (flag) {
if (!strcmp(flag->name, name))
return TRUE;
flag = flag->next;
}
return FALSE;
}
/**
* camel_flag_set:
* @list:
* @name:
* @value:
*
* Set the state of a flag @name in the list @list to @value.
**/
void
camel_flag_set(CamelFlag **list, const char *name, gboolean value)
{
CamelFlag *flag, *tmp;
/* 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;
}
flag = tmp;
}
if (value) {
tmp = g_malloc(sizeof(*tmp) + strlen(name));
strcpy(tmp->name, name);
tmp->next = 0;
flag->next = tmp;
}
}
/**
* camel_flag_list_size:
* @list:
*
* Get the length of the flag list.
*
* Return value: The number of TRUE flags in the list.
**/
int
camel_flag_list_size(CamelFlag **list)
{
int count=0;
CamelFlag *flag;
flag = *list;
while (flag) {
count++;
flag = flag->next;
}
return count;
}
/**
* camel_flag_list_free:
* @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;
}
const char *camel_tag_get(CamelTag **list, const char *name)
{
CamelTag *tag;
tag = *list;
while (tag) {
if (!strcmp(tag->name, name))
return (const char *)tag->value;
tag = tag->next;
}
return NULL;
}
/**
* camel_tag_set:
* @list:
* @name:
* @value:
*
* Set the tag @name in the tag list @list to @value.
**/
void camel_tag_set(CamelTag **list, const char *name, const char *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);
} else if (strcmp(tmp->value, value)) { /* has it changed? */
g_free(tmp->value);
tmp->value = g_strdup(value);
}
return;
}
tag = tmp;
}
if (value) {
tmp = g_malloc(sizeof(*tmp)+strlen(name));
strcpy(tmp->name, name);
tmp->value = g_strdup(value);
tmp->next = 0;
tag->next = tmp;
}
}
/**
* camel_tag_list_size:
* @list:
*
* Get the number of tags present in the tag list @list.
*
* Return value: The number of tags.
**/
int camel_tag_list_size(CamelTag **list)
{
int count=0;
CamelTag *tag;
tag = *list;
while (tag) {
count++;
tag = tag->next;
}
return count;
}
/**
* camel_tag_list_free:
* @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;
}
/**
* camel_message_info_dup_to:
* @from: source message info
* @to: destination message info
*
* Duplicates the contents of one CamelMessageInfo structure into another.
* (The destination is assumed to be empty: its contents are not freed.)
* The slightly odd interface is to allow this to be used to initialize
* "subclasses" of CamelMessageInfo.
**/
void
camel_message_info_dup_to(const CamelMessageInfo *from, CamelMessageInfo *to)
{
CamelFlag *flag;
CamelTag *tag;
/* Copy numbers */
to->flags = from->flags;
to->size = from->size;
to->date_sent = from->date_sent;
to->date_received = from->date_received;
/* Copy strings */
#ifdef DOESTRV
to->strings = e_strv_new(CAMEL_MESSAGE_INFO_LAST);
e_strv_set(to->strings, CAMEL_MESSAGE_INFO_SUBJECT, camel_message_info_subject(from));
e_strv_set(to->strings, CAMEL_MESSAGE_INFO_FROM, camel_message_info_from(from));
e_strv_set(to->strings, CAMEL_MESSAGE_INFO_TO, camel_message_info_to(from));
e_strv_set(to->strings, CAMEL_MESSAGE_INFO_CC, camel_message_info_cc(from));
e_strv_set(to->strings, CAMEL_MESSAGE_INFO_UID, camel_message_info_uid(from));
#else
to->subject = g_strdup(from->subject);
to->from = g_strdup(from->from);
to->to = g_strdup(from->to);
to->cc = g_strdup(from->cc);
to->uid = g_strdup(from->uid);
#endif
memcpy(&to->message_id, &from->message_id, sizeof(from->message_id));
/* Copy structures */
if (from->references) {
int len = sizeof(*from->references) + ((from->references->size-1) * sizeof(from->references->references[0]));
to->references = g_malloc(len);
memcpy(to->references, from->references, len);
} else {
to->references = NULL;
}
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;
}
/* No, this is impossible without knowing the class of summary we came from */
/* FIXME some day */
to->content = NULL;
}
/**
* camel_message_info_free:
* @mi: the message info
*
* Frees a CamelMessageInfo and its contents.
*
* Can only be used to free CamelMessageInfo's created with
* camel_message_info_dup_to.
*
**/
void
camel_message_info_free(CamelMessageInfo *mi)
{
#ifdef DOESTRV
e_strv_destroy(mi->strings);
#else
g_free(mi->uid);
g_free(mi->subject);
g_free(mi->from);
g_free(mi->to);
g_free(mi->cc);
#endif
g_free(mi->references);
camel_flag_list_free(&mi->user_flags);
camel_tag_list_free(&mi->user_tags);
/* FIXME: content info? */
g_free(mi);
}
#ifdef DOESTRV
const char *camel_message_info_string(const CamelMessageInfo *mi, int type)
{
if (mi->strings == NULL)
return "";
return e_strv_get(mi->strings, type);
}
void camel_message_info_set_string(CamelMessageInfo *mi, int type, char *str)
{
g_assert(mi->strings != NULL);
mi->strings = e_strv_set_ref_free(mi->strings, type, str);
}
#endif
#if 0
static void
content_info_dump(CamelMessageContentInfo *ci, int depth)
{
char *p;
p = alloca(depth*4+1);
memset(p, ' ', depth*4);
p[depth*4] = 0;
if (ci == NULL) {
printf("%s<empty>\n", p);
return;
}
printf("%sconent-type: %s/%s\n", p, ci->type->type, ci->type->subtype);
printf("%sontent-transfer-encoding: %s\n", p, ci->encoding);
printf("%scontent-description: %s\n", p, ci->description);
printf("%sbytes: %d %d %d\n", p, (int)ci->pos, (int)ci->bodypos, (int)ci->endpos);
ci = ci->childs;
while (ci) {
content_info_dump(ci, depth+1);
ci = ci->next;
}
}
static void
message_info_dump(CamelMessageInfo *mi)
{
if (mi == NULL) {
printf("No message?\n");
return;
}
printf("Subject: %s\n", camel_message_info_subject(mi));
printf("To: %s\n", camel_message_info_to(to));
printf("Cc: %s\n", camel_message_info_cc(cc));
printf("From: %s\n", camel_message_info_from(from));
printf("UID: %s\n", camel_message_info_uid(uid));
printf("Flags: %04x\n", mi->flags & 0xffff);
content_info_dump(mi->content, 0);
}
int main(int argc, char **argv)
{
CamelMimeParser *mp;
int fd;
CamelFolderSummary *s;
char *buffer;
int len;
int i;
ibex *index;
/*g_tk_init(&argc, &argv);*/
#if 0
{
int i;
char *s;
char buf[1024];
for (i=0;i<434712;i++) {
memcpy(buf, " ", 50);
buf[50] = 0;
#if 0
s = g_strdup(buf);
g_free(s);
#endif
}
return 0;
}
#endif
if (argc < 2 ) {
printf("usage: %s mbox\n", argv[0]);
return 1;
}
fd = open(argv[1], O_RDONLY);
index = ibex_open("index.ibex", O_CREAT|O_RDWR, 0600);
mp = camel_mime_parser_new();
camel_mime_parser_scan_from(mp, TRUE);
/* camel_mime_parser_set_header_regex(mp, "^(content-[^:]*|subject|from|to|date):");*/
camel_mime_parser_init_with_fd(mp, fd);
s = camel_folder_summary_new();
camel_folder_summary_set_build_content(s, TRUE);
/* camel_folder_summary_set_index(s, index);*/
while (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_FROM) {
/*printf("Parsing message ...\n");*/
camel_folder_summary_add_from_parser(s, mp);
if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM_END) {
g_warning("Uknown state encountered, excpecting %d, got %d\n", HSCAN_FROM_END, camel_mime_parser_state(mp));
break;
}
}
printf("Printing summary\n");
for (i=0;i<camel_folder_summary_count(s);i++) {
message_info_dump(camel_folder_summary_index(s, i));
}
printf("Saivng summary\n");
camel_folder_summary_set_filename(s, "index.summary");
camel_folder_summary_save(s);
{
CamelFolderSummary *n;
printf("\nLoading summary\n");
n = camel_folder_summary_new();
camel_folder_summary_set_build_content(n, TRUE);
camel_folder_summary_set_filename(n, "index.summary");
camel_folder_summary_load(n);
printf("Printing summary\n");
for (i=0;i<camel_folder_summary_count(n);i++) {
message_info_dump(camel_folder_summary_index(n, i));
}
camel_object_unref(n);
}
camel_object_unref(mp);
camel_object_unref(s);
printf("summarised %d messages\n", camel_folder_summary_count(s));
#if 0
printf("g_strdup count = %d\n", strdup_count);
printf("g_malloc count = %d\n", malloc_count);
printf("g_free count = %d\n", free_count);
#endif
return 0;
}
#endif