diff options
Diffstat (limited to 'camel/providers/imap/camel-imap-search.c')
-rw-r--r-- | camel/providers/imap/camel-imap-search.c | 506 |
1 files changed, 105 insertions, 401 deletions
diff --git a/camel/providers/imap/camel-imap-search.c b/camel/providers/imap/camel-imap-search.c index c30fa5611e..f0c5bb6c8b 100644 --- a/camel/providers/imap/camel-imap-search.c +++ b/camel/providers/imap/camel-imap-search.c @@ -4,9 +4,8 @@ /* * Authors: * Dan Winship <danw@ximian.com> - * Michael Zucchi <notzed@ximian.com> * - * Copyright 2000, 2001, 2002 Ximian, Inc. + * Copyright 2000, 2001 Ximian, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public @@ -36,59 +35,10 @@ #include "camel-imap-search.h" #include "camel-imap-private.h" #include "camel-imap-utils.h" -#include "camel-imap-summary.h" -#include "e-util/md5-utils.h" /* md5 hash building */ -#include "camel-mime-utils.h" /* base64 encoding */ - -#include "camel-seekable-stream.h" -#include "camel-search-private.h" - -#define d(x) x - -/* - File is: - BODY (as in body search) - Last uid when search performed - termcount: number of search terms - matchcount: number of matches - term0, term1 ... - match0, match1, match2, ... -*/ - -/* size of in-memory cache */ -#define MATCH_CACHE_SIZE (32) - -/* Also takes care of 'endianness' file magic */ -#define MATCH_MARK (('B' << 24) | ('O' << 16) | ('D' << 8) | 'Y') - -/* on-disk header, in native endianness format, matches follow */ -struct _match_header { - guint32 mark; - guint32 validity; /* uidvalidity for this folder */ - guint32 lastuid; - guint32 termcount; - guint32 matchcount; -}; - -/* in-memory record */ -struct _match_record { - struct _match_record *next; - struct _match_record *prev; - - char hash[17]; - - guint32 lastuid; - guint32 validity; - - unsigned int termcount; - char **terms; - GArray *matches; -}; - - -static void free_match(CamelImapSearch *is, struct _match_record *mr); -static ESExpResult *imap_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s); +static ESExpResult * +imap_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, + CamelFolderSearch *s); static CamelFolderSearchClass *imap_search_parent_class; @@ -99,33 +49,12 @@ camel_imap_search_class_init (CamelImapSearchClass *camel_imap_search_class) CamelFolderSearchClass *camel_folder_search_class = CAMEL_FOLDER_SEARCH_CLASS (camel_imap_search_class); - imap_search_parent_class = (CamelFolderSearchClass *)camel_type_get_global_classfuncs (camel_folder_search_get_type ()); + imap_search_parent_class = camel_type_get_global_classfuncs (camel_folder_search_get_type ()); /* virtual method overload */ camel_folder_search_class->body_contains = imap_body_contains; } -static void -camel_imap_search_init(CamelImapSearch *is) -{ - e_dlist_init(&is->matches); - is->matches_hash = g_hash_table_new(g_str_hash, g_str_equal); - is->matches_count = 0; - is->lastuid = 0; -} - -static void -camel_imap_search_finalise(CamelImapSearch *is) -{ - struct _match_record *mr; - - while ( (mr = (struct _match_record *)e_dlist_remtail(&is->matches)) ) - free_match(is, mr); - g_hash_table_destroy(is->matches_hash); - if (is->cache) - camel_object_unref((CamelObject *)is->cache); -} - CamelType camel_imap_search_get_type (void) { @@ -136,364 +65,139 @@ camel_imap_search_get_type (void) CAMEL_FOLDER_SEARCH_TYPE, "CamelImapSearch", sizeof (CamelImapSearch), sizeof (CamelImapSearchClass), - (CamelObjectClassInitFunc) camel_imap_search_class_init, NULL, - (CamelObjectInitFunc) camel_imap_search_init, - (CamelObjectFinalizeFunc) camel_imap_search_finalise); + (CamelObjectClassInitFunc) camel_imap_search_class_init, + NULL, NULL, NULL); } return camel_imap_search_type; } -/** - * camel_imap_search_new: - * - * Return value: A new CamelImapSearch widget. - **/ -CamelFolderSearch * -camel_imap_search_new (const char *cachedir) -{ - CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_imap_search_get_type ())); - CamelImapSearch *is = (CamelImapSearch *)new; - - camel_folder_search_construct (new); - - is->cache = camel_data_cache_new(cachedir, 0, NULL); - if (is->cache) { - /* Expire entries after 14 days of inactivity */ - camel_data_cache_set_expire_access(is->cache, 60*60*24*14); - } - - return new; -} - - -static void -hash_match(char hash[17], int argc, struct _ESExpResult **argv) -{ - MD5Context ctx; - unsigned char digest[16]; - unsigned int state = 0, save = 0; - int i; - - md5_init(&ctx); - for (i=0;i<argc;i++) { - if (argv[i]->type == ESEXP_RES_STRING) - md5_update(&ctx, argv[i]->value.string, strlen(argv[i]->value.string)); - } - md5_final(&ctx, digest); - - base64_encode_close(digest, 12, FALSE, hash, &state, &save); - - for (i=0;i<16;i++) { - if (hash[i] == '+') - hash[i] = ','; - if (hash[i] == '/') - hash[i] = '_'; - } - - hash[16] = 0; -} - -static int -save_match(CamelImapSearch *is, struct _match_record *mr) -{ - guint32 mark = MATCH_MARK; - int ret = 0; - struct _match_header header; - CamelStream *stream; - - /* since its a cache, doesn't matter if it doesn't save, at least we have the in-memory cache - for this session */ - if (is->cache == NULL) - return -1; - - stream = camel_data_cache_add(is->cache, "search/body-contains", mr->hash, NULL); - if (stream == NULL) - return -1; - - d(printf("Saving search cache entry to '%s': %s\n", mr->hash, mr->terms[0])); - - /* we write the whole thing, then re-write the header magic, saves fancy sync code */ - memcpy(&header.mark, " ", 4); - header.termcount = 0; - header.matchcount = mr->matches->len; - header.lastuid = mr->lastuid; - header.validity = mr->validity; - - if (camel_stream_write(stream, (char *)&header, sizeof(header)) != sizeof(header) - || camel_stream_write(stream, mr->matches->data, mr->matches->len*sizeof(guint32)) != mr->matches->len*sizeof(guint32) - || camel_seekable_stream_seek((CamelSeekableStream *)stream, 0, CAMEL_STREAM_SET) == -1 - || camel_stream_write(stream, (char *)&mark, sizeof(mark)) != sizeof(mark)) { - d(printf(" saving failed, removing cache entry\n")); - camel_data_cache_remove(is->cache, "search/body-contains", mr->hash, NULL); - ret = -1; - } - - camel_object_unref((CamelObject *)stream); - return ret; -} - -static void -free_match(CamelImapSearch *is, struct _match_record *mr) -{ - int i; - - for (i=0;i<mr->termcount;i++) - g_free(mr->terms[i]); - g_free(mr->terms); - g_array_free(mr->matches, TRUE); - g_free(mr); -} - -static struct _match_record * -load_match(CamelImapSearch *is, char hash[17], int argc, struct _ESExpResult **argv) -{ - struct _match_record *mr; - CamelStream *stream = NULL; - struct _match_header header; - int i; - - mr = g_malloc0(sizeof(*mr)); - mr->matches = g_array_new(0, 0, sizeof(guint32)); - g_assert(strlen(hash) == 16); - strcpy(mr->hash, hash); - mr->terms = g_malloc0(sizeof(mr->terms[0]) * argc); - for (i=0;i<argc;i++) { - if (argv[i]->type == ESEXP_RES_STRING) { - mr->termcount++; - mr->terms[i] = g_strdup(argv[i]->value.string); - } - } - - d(printf("Loading search cache entry to '%s': %s\n", mr->hash, mr->terms[0])); - - memset(&header, 0, sizeof(header)); - if (is->cache) - stream = camel_data_cache_get(is->cache, "search/body-contains", mr->hash, NULL); - if (stream != NULL) { - /* 'cause i'm gonna be lazy, i'm going to have the termcount == 0 for now, - and not load or save them since i can't think of a nice way to do it, the hash - should be sufficient to key it */ - /* This check should also handle endianness changes, we just throw away - the data (its only a cache) */ - if (camel_stream_read(stream, (char *)&header, sizeof(header)) == sizeof(header) - && header.validity == is->validity - && header.mark == MATCH_MARK - && header.termcount == 0) { - d(printf(" found %d matches\n", header.matchcount)); - g_array_set_size(mr->matches, header.matchcount); - camel_stream_read(stream, mr->matches->data, sizeof(guint32)*header.matchcount); - } else { - d(printf(" file format invalid/validity changed\n")); - memset(&header, 0, sizeof(header)); - } - camel_object_unref((CamelObject *)stream); - } else { - d(printf(" no cache entry found\n")); - } - - mr->validity = header.validity; - if (mr->validity != is->validity) - mr->lastuid = 0; - else - mr->lastuid = header.lastuid; - - return mr; -} - static int -sync_match(CamelImapSearch *is, struct _match_record *mr) +cmp_uid(const void *ap, const void *bp) { - char *p, *result, *lasts = NULL; - CamelImapResponse *response = NULL; - guint32 uid; - CamelFolder *folder = ((CamelFolderSearch *)is)->folder; - CamelImapStore *store = (CamelImapStore *)folder->parent_store; - struct _camel_search_words *words; - GString *search; - int i; - - if (mr->lastuid >= is->lastuid && mr->validity == is->validity) - return 0; + unsigned int a, b; - d(printf("updating match record for uid's %d:%d\n", mr->lastuid+1, is->lastuid)); - - /* TODO: Handle multiple search terms */ - - /* This handles multiple search words within a single term */ - words = camel_search_words_split(mr->terms[0]); - search = g_string_new(""); - g_string_sprintfa(search, "UID %d:%d", mr->lastuid+1, is->lastuid); - for (i=0;i<words->len;i++) { - char *w = words->words[i]->word, c; - - g_string_sprintfa(search, " BODY \""); - while ((c = *w++)) { - if (c == '\\' || c == '"') - g_string_append_c(search, '\\'); - g_string_append_c(search, c); - } - g_string_append_c(search, '"'); - } - camel_search_words_free(words); - - /* We only try search using utf8 if its non us-ascii text? */ - if ((words->type & CAMEL_SEARCH_WORD_8BIT) && (store->capabilities & IMAP_CAPABILITY_utf8_search)) { - response = camel_imap_command(store, folder, NULL, - "UID SEARCH CHARSET UTF-8 %s", search->str); - /* We can't actually tell if we got a NO response, so assume always */ - if (response == NULL) - store->capabilities &= ~IMAP_CAPABILITY_utf8_search; - } - if (response == NULL) - response = camel_imap_command (store, folder, NULL, - "UID SEARCH %s", search->str); - g_string_free(search, TRUE); - - if (!response) - return -1; - result = camel_imap_response_extract (store, response, "SEARCH", NULL); - if (!result) + a = strtoul(((char **)ap)[0], NULL, 10); + b = strtoul(((char **)bp)[0], NULL, 10); + if (a<b) return -1; - - p = result + sizeof ("* SEARCH"); - for (p = strtok_r (p, " ", &lasts); p; p = strtok_r (NULL, " ", &lasts)) { - uid = strtoul(p, NULL, 10); - g_array_append_vals(mr->matches, &uid, 1); - } - g_free(result); - - mr->validity = is->validity; - mr->lastuid = is->lastuid; - save_match(is, mr); + else if (a>b) + return 1; return 0; } -static struct _match_record * -get_match(CamelImapSearch *is, int argc, struct _ESExpResult **argv) -{ - char hash[17]; - struct _match_record *mr; - - hash_match(hash, argc, argv); - - mr = g_hash_table_lookup(is->matches_hash, hash); - if (mr == NULL) { - while (is->matches_count >= MATCH_CACHE_SIZE) { - mr = (struct _match_record *)e_dlist_remtail(&is->matches); - if (mr) { - printf("expiring match '%s' (%s)\n", mr->hash, mr->terms[0]); - g_hash_table_remove(is->matches_hash, mr->hash); - free_match(is, mr); - is->matches_count--; - } else { - is->matches_count = 0; - } - } - mr = load_match(is, hash, argc, argv); - g_hash_table_insert(is->matches_hash, mr->hash, mr); - is->matches_count++; - } else { - e_dlist_remove((EDListNode *)mr); - } - - e_dlist_addhead(&is->matches, (EDListNode *)mr); - - /* what about offline mode? */ - /* We could cache those results too, or should we cache them elsewhere? */ - sync_match(is, mr); - - return mr; -} - static ESExpResult * -imap_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s) +imap_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, + CamelFolderSearch *s) { CamelImapStore *store = CAMEL_IMAP_STORE (s->folder->parent_store); - CamelImapSearch *is = (CamelImapSearch *)s; - char *uid; + char *value = argv[0]->value.string; + CamelImapResponse *response = NULL; + char *result, *p, *lasts = NULL, *real_uid; + const char *uid = ""; ESExpResult *r; - CamelMessageInfo *info; + CamelMessageInfo *info; GHashTable *uid_hash = NULL; - GPtrArray *array; - int i, j; - struct _match_record *mr; - guint32 uidn, *uidp; - - d(printf("Performing body search '%s'\n", argv[0]->value.string)); - - /* TODO: Cache offline searches too? */ + char *set; + GPtrArray *sorted; + int i; /* If offline, search using the parent class, which can handle this manually */ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), NULL)) return imap_search_parent_class->body_contains(f, argc, argv, s); - /* optimise the match "" case - match everything */ - if (argc == 1 && argv[0]->value.string[0] == '\0') { - if (s->current) { - r = e_sexp_result_new(f, ESEXP_RES_BOOL); - r->value.bool = TRUE; - } else { - r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR); + if (s->current) { + uid = camel_message_info_uid (s->current); + r = e_sexp_result_new (f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + response = camel_imap_command (store, s->folder, NULL, + "UID SEARCH UID %s BODY \"%s\"", + uid, value); + } else { + r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR); + + if (argc == 1 && *value == '\0' && s->folder) { + /* optimise the match "" case - match everything */ r->value.ptrarray = g_ptr_array_new (); for (i = 0; i < s->summary->len; i++) { - info = g_ptr_array_index(s->summary, i); - g_ptr_array_add(r->value.ptrarray, (char *)camel_message_info_uid(info)); + CamelMessageInfo *info = g_ptr_array_index (s->summary, i); + g_ptr_array_add (r->value.ptrarray, (char *)camel_message_info_uid (info)); } - } - } else if (argc == 0 || s->summary->len == 0) { - /* nothing to match case, do nothing (should be handled higher up?) */ - if (s->current) { - r = e_sexp_result_new(f, ESEXP_RES_BOOL); - r->value.bool = FALSE; } else { - r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR); + /* If searching a (reasonably small) subset of + the real folder size, then use a + message-set to optimise it */ + /* TODO: This peeks a bunch of 'private'ish data */ + + if (s->summary->len < camel_folder_get_message_count(s->folder)/2) { + sorted = g_ptr_array_new(); + g_ptr_array_set_size(sorted, s->summary->len); + for (i=0;i<s->summary->len;i++) + sorted->pdata[i] = (void *)camel_message_info_uid((CamelMessageInfo *)s->summary->pdata[i]); + qsort(sorted->pdata, sorted->len, sizeof(sorted->pdata[0]), cmp_uid); + set = imap_uid_array_to_set(s->folder->summary, sorted); + response = camel_imap_command (store, s->folder, NULL, + "UID SEARCH UID %s BODY \"%s\"", + set, value); + g_free(set); + g_ptr_array_free(sorted, TRUE); + } else { + response = camel_imap_command (store, s->folder, NULL, + "UID SEARCH BODY \"%s\"", + value); + } + r->value.ptrarray = g_ptr_array_new (); } - } else { - int truth = FALSE; - - /* setup lastuid/validity for synchronising */ - info = g_ptr_array_index(s->summary, s->summary->len-1); - is->lastuid = strtoul(camel_message_info_uid(info), NULL, 10); - is->validity = ((CamelImapSummary *)(s->folder->summary))->validity; - - mr = get_match(is, argc, argv); - + } + + if (!response) + return r; + result = camel_imap_response_extract (store, response, "SEARCH", NULL); + if (!result) + return r; + + p = result + sizeof ("* SEARCH"); + for (p = strtok_r (p, " ", &lasts); p; p = strtok_r (NULL, " ", &lasts)) { if (s->current) { - uidn = strtoul(camel_message_info_uid(s->current), NULL, 10); - uidp = (guint32 *)mr->matches->data; - j = mr->matches->len; - for (i=0;i<j && !truth;i++) - truth = *uidp++ == uidn; - r = e_sexp_result_new(f, ESEXP_RES_BOOL); - r->value.bool = truth; - } else { - r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR); - array = r->value.ptrarray = g_ptr_array_new(); - - /* We use a hash to map the uid numbers to uid strings as required by the search api */ - /* We use the summary's strings so we dont need to alloc more */ - uid_hash = g_hash_table_new(NULL, NULL); - for (i = 0; i < s->summary->len; i++) { - info = s->summary->pdata[i]; - uid = (char *)camel_message_info_uid(info); - uidn = strtoul(uid, NULL, 10); - g_hash_table_insert(uid_hash, (void *)uidn, uid); + if (!strcmp (uid, p)) { + r->value.bool = TRUE; + break; } - - uidp = (guint32 *)mr->matches->data; - j = mr->matches->len; - for (i=0;i<j && !truth;i++) { - uid = g_hash_table_lookup(uid_hash, (void *)*uidp++); - if (uid) - g_ptr_array_add(array, uid); + } else { + /* if we need to setup a hash of summary items, this way we get + access to the summary memory which is locked for the duration of + the search, and wont vanish on us */ + if (uid_hash == NULL) { + uid_hash = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < s->summary->len; i++) { + info = s->summary->pdata[i]; + g_hash_table_insert (uid_hash, (char *)camel_message_info_uid (info), info); + } } - - g_hash_table_destroy(uid_hash); + if (g_hash_table_lookup_extended (uid_hash, p, (void *)&real_uid, (void *)&info)) + g_ptr_array_add (r->value.ptrarray, real_uid); } } - + + /* we could probably cache this globally, but its probably not worth it */ + if (uid_hash) + g_hash_table_destroy (uid_hash); + return r; } + +/** + * camel_imap_search_new: + * + * Return value: A new CamelImapSearch widget. + **/ +CamelFolderSearch * +camel_imap_search_new (void) +{ + CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_imap_search_get_type ())); + + camel_folder_search_construct (new); + return new; +} |