Dynamic LDAP queries
authorMatch Grun <match@dimensional.com>
Mon, 17 Mar 2003 06:49:25 +0000 (06:49 +0000)
committerMatch Grun <match@dimensional.com>
Mon, 17 Mar 2003 06:49:25 +0000 (06:49 +0000)
32 files changed:
ChangeLog.claws
configure.ac
src/Makefile.am
src/addr_compl.c
src/addr_compl.h
src/addrbook.c
src/addrbook.h
src/addrcache.c
src/addrcache.h
src/addrcindex.c [new file with mode: 0644]
src/addrcindex.h [new file with mode: 0644]
src/addressbook.c
src/addressbook.h
src/addrindex.c
src/addrindex.h
src/addritem.c
src/addritem.h
src/addrquery.h [new file with mode: 0644]
src/editaddress.c
src/editgroup.c
src/editldap.c
src/editldap_basedn.c
src/jpilot.c
src/ldapctrl.c [new file with mode: 0644]
src/ldapctrl.h [new file with mode: 0644]
src/ldapquery.c [new file with mode: 0644]
src/ldapquery.h [new file with mode: 0644]
src/ldapserver.c [new file with mode: 0644]
src/ldapserver.h [new file with mode: 0644]
src/ldaputil.c [new file with mode: 0644]
src/ldaputil.h [new file with mode: 0644]
src/vcard.c

index 4a4d719..2a010e4 100644 (file)
@@ -1,3 +1,26 @@
+2003-03-16 [match]     0.8.11claws20
+       * src/ldapserver.[ch]           ** NEW **
+       * src/ldapctrl.[ch]             ** NEW **
+       * src/ldapquery.[ch]            ** NEW **
+       * src/ldaputil.[ch]             ** NEW **
+       * src/addrcindex.[ch]           ** NEW **
+       * src/addrquery.h               ** NEW **
+       * src/addritem.[ch]
+       * src/addrcache.[ch]
+       * src/addrindex.[ch]
+       * src/addr_compl.[ch]
+       * src/addressbook.[ch]
+       * src/addrbook.[ch]
+       * src/editgroup.c
+       * src/editaddress.c
+       * src/editldap.c
+       * src/editldap_basedn.c
+       * src/vcard.c
+       * src/jpilot.c
+       * src/Makefile.am
+               included dynamic LDAP queries. improved address
+               completion.
+
 2003-03-16 [paul]      0.8.11claws19
 
        * tools/multiwebsearch.pl
index 4b5002a..324dadb 100644 (file)
@@ -11,7 +11,7 @@ MINOR_VERSION=8
 MICRO_VERSION=11
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=claws19
+EXTRA_VERSION=claws20
 VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
 
 dnl set $target
index 0e9ba0c..fd0b9b5 100644 (file)
@@ -35,8 +35,10 @@ sylpheed_SOURCES = \
        addr_compl.c addr_compl.h \
        addressitem.h \
        adbookbase.h \
+       addrquery.h \
        addritem.c addritem.h \
        addrcache.c addrcache.h \
+       addrcindex.c addrcindex.h \
        addrclip.c addrclip.h \
        addrselect.c addrselect.h \
        addrbook.c addrbook.h \
@@ -50,6 +52,10 @@ sylpheed_SOURCES = \
        importpine.c importpine.h \
        jpilot.c jpilot.h \
        syldap.c syldap.h \
+       ldapserver.c ldapserver.h \
+       ldapctrl.c ldapctrl.h \
+       ldapquery.c ldapquery.h \
+       ldaputil.c ldaputil.h \
        editbook.c editbook.h \
        editgroup.c editgroup.h \
        editaddress.c editaddress.h \
index 32aa5f4..7e4ee19 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
  *
- * Copyright (c) 2000-2001 by Alfons Hoogervorst <alfons@proteus.demon.nl>
+ * Copyright (c) 2000-2003 by Alfons Hoogervorst <alfons@proteus.demon.nl>
  *
  * 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
@@ -43,6 +43,9 @@
 #include "addressbook.h"
 #include "addr_compl.h"
 #include "utils.h"
+#include "addritem.h"
+#include "addrquery.h"
+#include <pthread.h>
 
 /* How it works:
  *
  * After calling the g_completion_complete(), we get a reference
  * to a valid email address.  
  *
- * Completion is very simplified. We never complete on another prefix,
- * i.e. we neglect the next smallest possible prefix for the current
+ * Completion is very simplified. We never complete on another searchTerm,
+ * i.e. we neglect the next smallest possible searchTerm for the current
  * completion cache. This is simply done so we might break up the
  * addresses a little more (e.g. break up alfons@proteus.demon.nl into
  * something like alfons, proteus, demon, nl; and then completing on
  * any of those words).
- */ 
-       
-/* address_entry - structure which refers to the original address entry in the
- * address book 
  */
-typedef struct
-{
-       gchar *name;
-       gchar *address;
-} address_entry;
 
-/* completion_entry - structure used to complete addresses, with a reference
- * the the real address information.
- */
-typedef struct
-{
-       gchar           *string; /* string to complete */
-       address_entry   *ref;    /* address the string belongs to  */
-} completion_entry;
+typedef struct _CompletionWindow CompletionWindow;
+struct _CompletionWindow {
+       gint      listCount;
+       gchar     *searchTerm;
+       GtkWidget *window;
+       GtkWidget *entry;
+       GtkWidget *clist;
+};
 
-/*******************************************************************************/
+/**
+ * Current query ID.
+ */
+static gint _queryID_ = 0;
 
-static gint        g_ref_count;        /* list ref count */
-static GList      *g_completion_list;  /* list of strings to be checked */
-static GList      *g_address_list;     /* address storage */
-static GCompletion *g_completion;      /* completion object */
+/**
+ * Completion idle ID.
+ */
+static gint _completionIdleID_ = 0;
 
-/* To allow for continuing completion we have to keep track of the state
- * using the following variables. No need to create a context object. */
+/**
+ * Completion window.
+ */
+static CompletionWindow *_compWindow_ = NULL;
 
-static gint        g_completion_count;         /* nr of addresses incl. the prefix */
-static gint        g_completion_next;          /* next prev address */
-static GSList     *g_completion_addresses;     /* unique addresses found in the
-                                                  completion cache. */
-static gchar      *g_completion_prefix;        /* last prefix. (this is cached here
-                                                * because the prefix passed to g_completion
-                                                * is g_strdown()'ed */
+/**
+ * Mutex to protect callback from multiple threads.
+ */
+static pthread_mutex_t _completionMutex_ = PTHREAD_MUTEX_INITIALIZER;
 
-/*******************************************************************************/
+/**
+ * Completion queue list.
+ */
+static GList *_displayQueue_ = NULL;
 
-/* completion_func() - used by GTK to find the string data to be used for 
- * completion 
+/**
+ * Create a completion window object.
+ * \return Initialized completion window.
  */
-static gchar *completion_func(gpointer data)
-{
-       g_return_val_if_fail(data != NULL, NULL);
+static CompletionWindow *addrcompl_create_window( void ) {
+       CompletionWindow *cw;
 
-       return ((completion_entry *)data)->string;
-} 
+       cw = g_new0( CompletionWindow, 1 );
+       cw->listCount = 0;
+       cw->searchTerm = NULL;
+       cw->window = NULL;
+       cw->entry = NULL;
+       cw->clist = NULL;
 
-static void init_all(void)
-{
-       g_completion = g_completion_new(completion_func);
-       g_return_if_fail(g_completion != NULL);
+       return cw;      
 }
 
-static void free_all(void)
-{
-       GList *walk;
-       
-       walk = g_list_first(g_completion_list);
-       for (; walk != NULL; walk = g_list_next(walk)) {
-               completion_entry *ce = (completion_entry *) walk->data;
-               g_free(ce->string);
-               g_free(walk->data);
-       }
-       g_list_free(g_completion_list);
-       g_completion_list = NULL;
-       
-       walk = g_address_list;
-       for (; walk != NULL; walk = g_list_next(walk)) {
-               address_entry *ae = (address_entry *) walk->data;
-               g_free(ae->name);
-               g_free(ae->address);
-               g_free(walk->data);
+/**
+ * Destroy completion window.
+ * \param cw Window to destroy.
+ */
+static void addrcompl_destroy_window( CompletionWindow *cw ) {
+       /* Remove idler function... or application may not terminate */
+       if( _completionIdleID_ != 0 ) {
+               gtk_idle_remove( _completionIdleID_ );
+               _completionIdleID_ = 0;
        }
-       g_list_free(g_address_list);
-       g_address_list = NULL;
-       
-       g_completion_free(g_completion);
-       g_completion = NULL;
-}
 
-static void add_address1(const char *str, address_entry *ae)
-{
-       completion_entry *ce1;
-       ce1 = g_new0(completion_entry, 1),
-       ce1->string = g_strdup(str);
-       /* GCompletion list is case sensitive */
-       g_strdown(ce1->string);
-       ce1->ref = ae;
-
-       g_completion_list = g_list_prepend(g_completion_list, ce1);
+       /* Now destroy window */        
+       if( cw ) {
+               /* Clear references to widgets */
+               cw->entry = NULL;
+               cw->clist = NULL;
+
+               /* Free objects */
+               if( cw->window ) {
+                       gtk_widget_hide( cw->window );
+                       gtk_widget_destroy( cw->window );
+               }
+               cw->window = NULL;
+       }
 }
 
-/* add_address() - adds address to the completion list. this function looks
- * complicated, but it's only allocation checks.
+/**
+ * Free up completion window.
+ * \param cw Window to free.
  */
-static gint add_address(const gchar *name, const gchar *address, const gchar *alias)
-{
-       address_entry    *ae;
-
-       if (!name || !address) return -1;
-
-       ae = g_new0(address_entry, 1);
+static void addrcompl_free_window( CompletionWindow *cw ) {
+       /* printf( "addrcompl_free_window...\n" ); */
+       if( cw ) {
+               addrcompl_destroy_window( cw );
 
-       g_return_val_if_fail(ae != NULL, -1);
-
-       ae->name    = g_strdup(name);
-       ae->address = g_strdup(address);                
-
-       g_address_list    = g_list_prepend(g_address_list,    ae);
-
-       add_address1(name, ae);
-       add_address1(address, ae);
-       add_address1(alias, ae);
-
-       return 0;
-}
+               g_free( cw->searchTerm );
+               cw->searchTerm = NULL;
 
-/* read_address_book()
- */ 
-static void read_address_book(void) {  
-       addressbook_load_completion( add_address );
-       g_address_list = g_list_reverse(g_address_list);
-       g_completion_list = g_list_reverse(g_completion_list);
-}
+               /* Clear references */          
+               cw->listCount = 0;
 
-/* start_address_completion() - returns the number of addresses 
- * that should be matched for completion.
- */
-gint start_address_completion(void)
-{
-       clear_completion_cache();
-       if (!g_ref_count) {
-               init_all();
-               /* open the address book */
-               read_address_book();
-               /* merge the completion entry list into g_completion */
-               if (g_completion_list)
-                       g_completion_add_items(g_completion, g_completion_list);
+               /* Free object */               
+               g_free( cw );
        }
-       g_ref_count++;
-       debug_print("start_address_completion ref count %d\n", g_ref_count);
-
-       return g_list_length(g_completion_list);
+       /* printf( "addrcompl_free_window...done\n" ); */
 }
 
-/* get_address_from_edit() - returns a possible address (or a part)
- * from an entry box. To make life easier, we only look at the last valid address 
- * component; address completion only works at the last string component in
- * the entry box. 
+/**
+ * Retrieve a possible address (or a part) from an entry box. To make life
+ * easier, we only look at the last valid address component; address
+ * completion only works at the last string component in the entry box.
+ *
+ * \param entry Address entry field.
+ * \param start_pos Address of start position of address.
+ * \return Possible address.
  */ 
-gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
+static gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
 {
        const gchar *edit_text;
        gint cur_pos;
@@ -279,11 +238,15 @@ gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
        g_free(wtext);
 
        return str;
-} 
+}
 
-/* replace_address_in_edit() - replaces an incompleted address with a completed one.
+/**
+ * Replace text in entry field with specified address.
+ * \param entry     Address entry field.
+ * \param newtext   New text.
+ * \param start_pos Insertion point in entry field.
  */
-void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
+static void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
                             gint start_pos)
 {
        if (!newtext) return;
@@ -293,370 +256,286 @@ void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
        gtk_editable_set_position(GTK_EDITABLE(entry), -1);
 }
 
-/* complete_address() - tries to complete an addres, and returns the
- * number of addresses found. use get_complete_address() to get one.
- * returns zero if no match was found, otherwise the number of addresses,
- * with the original prefix at index 0. 
+/**
+ * Format E-Mail address.
+ * \param email EMail item to format.
+ * \return Formatted string. Should be freed after use.
  */
-guint complete_address(const gchar *str)
-{
-       GList *result;
-       gchar *d;
-       guint  count, cpl;
-       completion_entry *ce;
-
-       g_return_val_if_fail(str != NULL, 0);
+static gchar *addrcompl_format_email( ItemEMail *email ) {
+       gchar *address;
+       gchar *name;
+       ItemPerson *person;
 
-       Xstrdup_a(d, str, return 0);
-
-       clear_completion_cache();
-       g_completion_prefix = g_strdup(str);
-
-       /* g_completion is case sensitive */
-       g_strdown(d);
-       result = g_completion_complete(g_completion, d, NULL);
-
-       count = g_list_length(result);
-       if (count) {
-               /* create list with unique addresses  */
-               for (cpl = 0, result = g_list_first(result);
-                    result != NULL;
-                    result = g_list_next(result)) {
-                       ce = (completion_entry *)(result->data);
-                       if (NULL == g_slist_find(g_completion_addresses,
-                                                ce->ref)) {
-                               cpl++;
-                               g_completion_addresses =
-                                       g_slist_append(g_completion_addresses,
-                                                      ce->ref);
-                       }
+       address = NULL;
+       name = NULL;
+       if( ADDRITEM_NAME( email ) ) {
+               if( strlen( ADDRITEM_NAME( email ) ) ) {
+                       name = ADDRITEM_NAME( email );
                }
-               count = cpl + 1;        /* index 0 is the original prefix */
-               g_completion_next = 1;  /* we start at the first completed one */
-       } else {
-               g_free(g_completion_prefix);
-               g_completion_prefix = NULL;
+       }
+       if( ! name ) {
+               person = ( ItemPerson * ) ADDRITEM_PARENT( email );
+               name = ADDRITEM_NAME( person );
        }
 
-       g_completion_count = count;
-       return count;
-}
-
-/* get_complete_address() - returns a complete address. the returned
- * string should be freed 
- */
-gchar *get_complete_address(gint index)
-{
-       const address_entry *p;
-       gchar *address = NULL;
-
-       if (index < g_completion_count) {
-               if (index == 0)
-                       address = g_strdup(g_completion_prefix);
+       if( name ) {
+               if( strchr_with_skip_quote( name, '"', ',' ) ) {
+                       address = g_strdup_printf( "\"%s\" <%s>", name, email->address );
+               }
                else {
-                       /* get something from the unique addresses */
-                       p = (address_entry *)g_slist_nth_data
-                               (g_completion_addresses, index - 1);
-                       if (p != NULL) {
-                               if (!p->name || p->name[0] == '\0')
-                                       address = g_strdup_printf(p->address);
-                               else if (strchr_with_skip_quote(p->name, '"', ','))
-                                       address = g_strdup_printf
-                                               ("\"%s\" <%s>", p->name, p->address);
-                               else
-                                       address = g_strdup_printf
-                                               ("%s <%s>", p->name, p->address);
-                       }
+                       address = g_strdup_printf( "%s <%s>", name, email->address );
                }
        }
-
+       else {
+               address = g_strdup_printf( "%s", email->address );
+       }
        return address;
 }
 
-gchar *get_next_complete_address(void)
-{
-       if (is_completion_pending()) {
-               gchar *res;
+/**
+ * Resize window to accommodate maximum number of address entries.
+ * \param cw Completion window.
+ */
+static void addrcompl_resize_window( CompletionWindow *cw ) {
+       GtkRequisition r;
+       gint x, y, width, height, depth;
 
-               res = get_complete_address(g_completion_next);
-               g_completion_next += 1;
-               if (g_completion_next >= g_completion_count)
-                       g_completion_next = 0;
+       /* Get current geometry of window */
+       gdk_window_get_geometry( cw->window->window, &x, &y, &width, &height, &depth );
 
-               return res;
-       } else
-               return NULL;
-}
+       gtk_widget_size_request( cw->clist, &r );
+       gtk_widget_set_usize( cw->window, width, r.height );
+       gtk_widget_show_all( cw->window );
+       gtk_widget_size_request( cw->clist, &r );
 
-gchar *get_prev_complete_address(void)
-{
-       if (is_completion_pending()) {
-               int n = g_completion_next - 2;
-
-               /* real previous */
-               n = (n + (g_completion_count * 5)) % g_completion_count;
-
-               /* real next */
-               g_completion_next = n + 1;
-               if (g_completion_next >=  g_completion_count)
-                       g_completion_next = 0;
-               return get_complete_address(n);
-       } else
-               return NULL;
+       /* Adjust window height to available screen space */
+       if( ( y + r.height ) > gdk_screen_height() ) {
+               gtk_window_set_policy( GTK_WINDOW( cw->window ), TRUE, FALSE, FALSE );
+               gtk_widget_set_usize( cw->window, width, gdk_screen_height() - y );
+       }
 }
 
-guint get_completion_count(void)
-{
-       if (is_completion_pending())
-               return g_completion_count;
-       else
-               return 0;
-}
+/**
+ * Add an address the completion window address list.
+ * \param cw Completion window.
+ * \param address Address to add.
+ */
+static void addrcompl_add_entry( CompletionWindow *cw, gchar *address ) {
+       gchar *text[] = { NULL, NULL };
 
-/* should clear up anything after complete_address() */
-void clear_completion_cache(void)
-{
-       if (is_completion_pending()) {
-               if (g_completion_prefix)
-                       g_free(g_completion_prefix);
+       /* printf( "\t\tAdding :%s\n", address ); */
+       text[0] = address;
+       gtk_clist_append( GTK_CLIST(cw->clist), text );
+       cw->listCount++;
 
-               if (g_completion_addresses) {
-                       g_slist_free(g_completion_addresses);
-                       g_completion_addresses = NULL;
-               }
+       /* Resize window */
+       addrcompl_resize_window( cw );
+       gtk_grab_add( cw->window );
 
-               g_completion_count = g_completion_next = 0;
+       if( cw->listCount == 2 ) {
+               /* Move off first row */
+               gtk_clist_select_row( GTK_CLIST(cw->clist), 1, 0);
        }
 }
 
-gboolean is_completion_pending(void)
-{
-       /* check if completion pending, i.e. we might satisfy a request for the next
-        * or previous address */
-        return g_completion_count;
+/**
+ * Completion idle function. This function is called by the main (UI) thread
+ * during UI idle time while an address search is in progress. Items from the
+ * display queue are processed and appended to the address list.
+ *
+ * \param data Target completion window to receive email addresses.
+ * \return <i>TRUE</i> to ensure that idle event do not get ignored.
+ */
+static gboolean addrcompl_idle( gpointer data ) {
+       GList *node;
+       gchar *address;
+       CompletionWindow *cw;
+
+       /* Process all entries in display queue */
+       pthread_mutex_lock( & _completionMutex_ );
+       if( _displayQueue_ ) {
+               cw = data;
+               node = _displayQueue_;
+               while( node ) {
+                       ItemEMail *email = node->data;
+                       /* printf( "email/address ::%s::\n", email->address ); */
+                       address = addrcompl_format_email( email );
+                       /* printf( "address ::: %s :::\n", address ); */
+                       addrcompl_add_entry( cw, address );
+                       g_free( address );
+                       node = g_list_next( node );
+               }
+               g_list_free( _displayQueue_ );
+               _displayQueue_ = NULL;
+       }
+       pthread_mutex_unlock( & _completionMutex_ );
+
+       return TRUE;
 }
 
-/* invalidate_address_completion() - should be called if address book
- * changed; 
+/**
+ * Callback entry point. The background thread (if any) appends the address
+ * list to the display queue.
+ * \param queryID    Query ID of search request.
+ * \param listEMail  List of zero of more email objects that met search
+ *                   criteria.
+ * \param target     Target object to received data.
  */
-gint invalidate_address_completion(void)
+static gint addrcompl_callback(
+       gint queryID, GList *listEMail, gpointer target )
 {
-       if (g_ref_count) {
-               /* simply the same as start_address_completion() */
-               debug_print("Invalidation request for address completion\n");
-               free_all();
-               init_all();
-               read_address_book();
-               g_completion_add_items(g_completion, g_completion_list);
-               clear_completion_cache();
-       }
+       GList *node;
+       gchar *address;
 
-       return g_list_length(g_completion_list);
+       pthread_mutex_lock( & _completionMutex_ );
+       if( queryID == _queryID_ ) {
+               /* Append contents to end of display queue */
+               node = listEMail;
+               while( node ) {
+                       ItemEMail *email = node->data;
+                       if( target ) {
+                               /*
+                               printf( "\temail/address ::%s::\n", email->address );
+                               */
+                               _displayQueue_ = g_list_append( _displayQueue_, email );
+                       }
+                       node = g_list_next( node );
+               }
+       }
+       pthread_mutex_unlock( & _completionMutex_ );
 }
 
-gint end_address_completion(void)
-{
-       clear_completion_cache();
+/**
+ * Start the search.
+ */
+static void addrcompl_start_search( void ) {
+       gchar *searchTerm;
 
-       if (0 == --g_ref_count)
-               free_all();
+       searchTerm = g_strdup( _compWindow_->searchTerm );
 
-       debug_print("end_address_completion ref count %d\n", g_ref_count);
+       /* Clear out display queue */
+       pthread_mutex_lock( & _completionMutex_ );
 
-       return g_ref_count; 
-}
+       g_list_free( _displayQueue_ );
+       _displayQueue_ = NULL;
 
+       pthread_mutex_unlock( & _completionMutex_ );
 
-/* address completion entry ui. the ui (completion list was inspired by galeon's
+       /* Setup the search */
+       _queryID_ = addressbook_setup_search(
+               searchTerm, _compWindow_, addrcompl_callback );
+       g_free( searchTerm );
+
+       /* Sit back and wait until something happens */
+       _completionIdleID_ =
+               gtk_idle_add( ( GtkFunction ) addrcompl_idle, _compWindow_ );
+       addressbook_start_search( _queryID_ );
+}
+
+/*
+ * address completion entry ui. the ui (completion list was inspired by galeon's
  * auto completion list). remaining things powered by sylpheed's completion engine.
  */
 
-#define ENTRY_DATA_TAB_HOOK    "tab_hook"                      /* used to lookup entry */
-#define WINDOW_DATA_COMPL_ENTRY        "compl_entry"   /* used to store entry for compl. window */
-#define WINDOW_DATA_COMPL_CLIST "compl_clist"  /* used to store clist for compl. window */
+#define ENTRY_DATA_TAB_HOOK    "tab_hook"      /* used to lookup entry */
 
-static void address_completion_mainwindow_set_focus    (GtkWindow   *window,
-                                                        GtkWidget   *widget,
-                                                        gpointer     data);
 static gboolean address_completion_entry_key_pressed   (GtkEntry    *entry,
                                                         GdkEventKey *ev,
                                                         gpointer     data);
 static gboolean address_completion_complete_address_in_entry
-                                                       (GtkEntry    *entry,
-                                                        gboolean     next);
+                                                       (GtkEntry    *entry);
+
 static void address_completion_create_completion_window        (GtkEntry    *entry);
 
 static void completion_window_select_row(GtkCList       *clist,
                                         gint             row,
                                         gint             col,
                                         GdkEvent        *event,
-                                        GtkWidget      **completion_window);
+                                        CompletionWindow *compWin );
+
 static gboolean completion_window_button_press
                                        (GtkWidget       *widget,
                                         GdkEventButton  *event,
-                                        GtkWidget      **completion_window);
+                                        CompletionWindow *compWin );
+
 static gboolean completion_window_key_press
                                        (GtkWidget       *widget,
                                         GdkEventKey     *event,
-                                        GtkWidget      **completion_window);
+                                        CompletionWindow *compWin );
 
 
-static void completion_window_advance_to_row(GtkCList *clist, gint row)
-{
-       g_return_if_fail(row < g_completion_count);
-       gtk_clist_select_row(clist, row, 0);
-}
-
+/**
+ * Advance selection to previous/next item in list.
+ * \param clist   List to process.
+ * \param forward Set to <i>TRUE</i> to select next or <i>FALSE</i> for
+ *                previous entry.
+ */
 static void completion_window_advance_selection(GtkCList *clist, gboolean forward)
 {
        int row;
 
        g_return_if_fail(clist != NULL);
-       g_return_if_fail(clist->selection != NULL);
-
-       row = GPOINTER_TO_INT(clist->selection->data);
-
-       row = forward ? (row + 1) % g_completion_count :
-                       (row - 1) < 0 ? g_completion_count - 1 : row - 1;
-
-       gtk_clist_freeze(clist);
-       completion_window_advance_to_row(clist, row);                                   
-       gtk_clist_thaw(clist);
-}
-
-#if 0
-/* completion_window_accept_selection() - accepts the current selection in the
- * clist, and destroys the window */
-static void completion_window_accept_selection(GtkWidget **window,
-                                              GtkCList *clist,
-                                              GtkEntry *entry)
-{
-       gchar *address = NULL, *text = NULL;
-       gint   cursor_pos, row;
-
-       g_return_if_fail(window != NULL);
-       g_return_if_fail(*window != NULL);
-       g_return_if_fail(clist != NULL);
-       g_return_if_fail(entry != NULL);
-       g_return_if_fail(clist->selection != NULL);
-
-       /* FIXME: I believe it's acceptable to access the selection member directly  */
-       row = GPOINTER_TO_INT(clist->selection->data);
-
-       /* we just need the cursor position */
-       address = get_address_from_edit(entry, &cursor_pos);
-       g_free(address);
-       gtk_clist_get_text(clist, row, 0, &text);
-       replace_address_in_edit(entry, text, cursor_pos);
 
-       clear_completion_cache();
-       gtk_widget_destroy(*window);
-       *window = NULL;
+       if( clist->selection ) {
+               row = GPOINTER_TO_INT(clist->selection->data);
+               row = forward ? ( row + 1 ) : ( row - 1 );
+               gtk_clist_freeze(clist);
+               gtk_clist_select_row(clist, row, 0);
+               gtk_clist_thaw(clist);
+       }
 }
-#endif
 
-/* completion_window_apply_selection() - apply the current selection in the
- * clist */
+/**
+ * Apply the current selection in the list to the entry field. Focus is also
+ * moved to the next widget so that Tab key works correctly.
+ * \param clist List to process.
+ * \param entry Address entry field.
+ */
 static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry)
 {
        gchar *address = NULL, *text = NULL;
        gint   cursor_pos, row;
+       GtkWidget *parent;
 
        g_return_if_fail(clist != NULL);
        g_return_if_fail(entry != NULL);
        g_return_if_fail(clist->selection != NULL);
 
+       /* First remove the idler */
+       if( _completionIdleID_ != 0 ) {
+               gtk_idle_remove( _completionIdleID_ );
+               _completionIdleID_ = 0;
+       }
+
+       /* Process selected item */
        row = GPOINTER_TO_INT(clist->selection->data);
 
        address = get_address_from_edit(entry, &cursor_pos);
        g_free(address);
        gtk_clist_get_text(clist, row, 0, &text);
        replace_address_in_edit(entry, text, cursor_pos);
-}
-
-/* should be called when creating the main window containing address
- * completion entries */
-void address_completion_start(GtkWidget *mainwindow)
-{
-       start_address_completion();
-
-       /* register focus change hook */
-       gtk_signal_connect(GTK_OBJECT(mainwindow), "set_focus",
-                          GTK_SIGNAL_FUNC(address_completion_mainwindow_set_focus),
-                          mainwindow);
-}
-
-/* Need unique data to make unregistering signal handler possible for the auto
- * completed entry */
-#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
-
-void address_completion_register_entry(GtkEntry *entry)
-{
-       g_return_if_fail(entry != NULL);
-       g_return_if_fail(GTK_IS_ENTRY(entry));
-
-       /* add hooked property */
-       gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
-
-       /* add keypress event */
-       gtk_signal_connect_full(GTK_OBJECT(entry), "key_press_event",
-                               GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
-                               NULL,
-                               COMPLETION_UNIQUE_DATA,
-                               NULL,
-                               0,
-                               0); /* magic */
-}
-
-void address_completion_unregister_entry(GtkEntry *entry)
-{
-       GtkObject *entry_obj;
-
-       g_return_if_fail(entry != NULL);
-       g_return_if_fail(GTK_IS_ENTRY(entry));
-
-       entry_obj = gtk_object_get_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
-       g_return_if_fail(entry_obj);
-       g_return_if_fail(entry_obj == GTK_OBJECT(entry));
-
-       /* has the hooked property? */
-       gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
 
-       /* remove the hook */
-       gtk_signal_disconnect_by_func(GTK_OBJECT(entry), 
-               GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
-               COMPLETION_UNIQUE_DATA);
-}
-
-/* should be called when main window with address completion entries
- * terminates.
- * NOTE: this function assumes that it is called upon destruction of
- * the window */
-void address_completion_end(GtkWidget *mainwindow)
-{
-       /* if address_completion_end() is really called on closing the window,
-        * we don't need to unregister the set_focus_cb */
-       end_address_completion();
-}
+       /* Move focus to next widget */
+       parent = GTK_WIDGET(entry)->parent;
+       if( parent ) {
+               gtk_container_focus( GTK_CONTAINER(parent), GTK_DIR_TAB_FORWARD );
+       }
 
-/* if focus changes to another entry, then clear completion cache */
-static void address_completion_mainwindow_set_focus(GtkWindow *window,
-                                                   GtkWidget *widget,
-                                                   gpointer   data)
-{
-       if (widget)
-               clear_completion_cache();
 }
 
-/* watch for tabs in one of the address entries. if no tab then clear the
- * completion cache */
+/**
+ * Listener that watches for tab or other keystroke in address entry field.
+ * \param entry Address entry field.
+ * \param ev    Event object.
+ * \param data  User data.
+ * \return <i>TRUE</i>.
+ */
 static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
                                                     GdkEventKey *ev,
                                                     gpointer     data)
 {
        if (ev->keyval == GDK_Tab) {
-               if (address_completion_complete_address_in_entry(entry, TRUE)) {
-                       address_completion_create_completion_window(entry);
+               if (address_completion_complete_address_in_entry(entry)) {
                        /* route a void character to the default handler */
                        /* this is a dirty hack; we're actually changing a key
                         * reported by the system. */
@@ -664,193 +543,173 @@ static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
                        ev->state &= ~GDK_SHIFT_MASK;
                        gtk_signal_emit_stop_by_name(GTK_OBJECT(entry),
                                                     "key_press_event");
-               } else {
-                       /* old behaviour */
-               }
-       } else if (ev->keyval == GDK_Shift_L
-               || ev->keyval == GDK_Shift_R
-               || ev->keyval == GDK_Control_L
-               || ev->keyval == GDK_Control_R
-               || ev->keyval == GDK_Caps_Lock
-               || ev->keyval == GDK_Shift_Lock
-               || ev->keyval == GDK_Meta_L
-               || ev->keyval == GDK_Meta_R
-               || ev->keyval == GDK_Alt_L
-               || ev->keyval == GDK_Alt_R) {
-               /* these buttons should not clear the cache... */
-       } else
-               clear_completion_cache();
 
+                       /* printf( "entry_key_pressed::create window\n" ); */
+                       address_completion_create_completion_window( entry );
+                       /* printf( "entry_key_pressed::create window...done\n" ); */
+                       addrcompl_start_search();
+               }
+       }
        return TRUE;
 }
 
-/* initialize the completion cache and put first completed string
- * in entry. this function used to do back cycling but this is not
- * currently used. since the address completion behaviour has been
- * changed regularly, we keep the feature in case someone changes
- * his / her mind again. :) */
-static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
-                                                            gboolean  next)
+/**
+ * Initialize search term for address completion.
+ * \param entry Address entry field.
+ */
+static gboolean address_completion_complete_address_in_entry( GtkEntry *entry )
 {
-       gint ncount, cursor_pos;
-       gchar *address, *new = NULL;
-       gboolean completed = FALSE;
+       gint cursor_pos;
+       gchar *searchTerm;
 
        g_return_val_if_fail(entry != NULL, FALSE);
 
        if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
 
        /* get an address component from the cursor */
-       address = get_address_from_edit(entry, &cursor_pos);
-       if (!address) return FALSE;
-
-       /* still something in the cache */
-       if (is_completion_pending()) {
-               new = next ? get_next_complete_address() :
-                       get_prev_complete_address();
-       } else {
-               if (0 < (ncount = complete_address(address)))
-                       new = get_next_complete_address();
-       }
+       searchTerm = get_address_from_edit( entry, &cursor_pos );
+       if (! searchTerm ) return FALSE;
 
-       if (new) {
-               /* prevent "change" signal */
-               /* replace_address_in_edit(entry, new, cursor_pos); */
-               g_free(new);
-               completed = TRUE;
+       /* Clear any existing search */
+       if( _compWindow_->searchTerm ) {
+               g_free( _compWindow_->searchTerm );
        }
 
-       g_free(address);
+       /* Replace with new search term */
+       _compWindow_->searchTerm = searchTerm;
 
-       return completed;
+       return TRUE;
 }
 
-static void address_completion_create_completion_window(GtkEntry *entry_)
+/**
+ * Create new address completion window for specified entry.
+ * \param entry_ Entry widget to associate with window.
+ */
+static void address_completion_create_completion_window( GtkEntry *entry_ )
 {
-       static GtkWidget *completion_window;
        gint x, y, height, width, depth;
        GtkWidget *scroll, *clist;
        GtkRequisition r;
-       guint count = 0;
+       GtkWidget *window;
        GtkWidget *entry = GTK_WIDGET(entry_);
+       gchar *searchTerm;
 
-       if (completion_window) {
-               gtk_widget_destroy(completion_window);
-               completion_window = NULL;
-       }
-
-       scroll = gtk_scrolled_window_new(NULL, NULL);
+       /* Create new window and list */
+       window = gtk_window_new(GTK_WINDOW_POPUP);
        clist  = gtk_clist_new(1);
-       gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
-       
-       completion_window = gtk_window_new(GTK_WINDOW_POPUP);
 
+       /* Destroy any existing window */
+       addrcompl_destroy_window( _compWindow_ );
+
+       /* Create new object */
+       _compWindow_->window = window;
+       _compWindow_->entry = entry;
+       _compWindow_->clist = clist;
+       _compWindow_->listCount = 0;
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
                                       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
-       gtk_container_add(GTK_CONTAINER(completion_window), scroll);
+       gtk_container_add(GTK_CONTAINER(window), scroll);
        gtk_container_add(GTK_CONTAINER(scroll), clist);
+       gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
 
-       /* set the unique data so we can always get back the entry and
-        * clist window to which this completion window has been attached */
-       gtk_object_set_data(GTK_OBJECT(completion_window),
-                           WINDOW_DATA_COMPL_ENTRY, entry_);
-       gtk_object_set_data(GTK_OBJECT(completion_window),
-                           WINDOW_DATA_COMPL_CLIST, clist);
-
-       gtk_signal_connect(GTK_OBJECT(clist), "select_row",
-                          GTK_SIGNAL_FUNC(completion_window_select_row),
-                          &completion_window);
-
-       for (count = 0; count < get_completion_count(); count++) {
-               gchar *text[] = {NULL, NULL};
-
-               text[0] = get_complete_address(count);
-               gtk_clist_append(GTK_CLIST(clist), text);
-               g_free(text[0]);
-       }
-
+       /* Use entry widget to create initial window */
        gdk_window_get_geometry(entry->window, &x, &y, &width, &height, &depth);
        gdk_window_get_deskrelative_origin (entry->window, &x, &y);
        y += height;
-       gtk_widget_set_uposition(completion_window, x, y);
-
-       gtk_widget_size_request(clist, &r);
-       gtk_widget_set_usize(completion_window, width, r.height);
-       gtk_widget_show_all(completion_window);
-       gtk_widget_size_request(clist, &r);
-
-       if ((y + r.height) > gdk_screen_height()) {
-               gtk_window_set_policy(GTK_WINDOW(completion_window),
-                                     TRUE, FALSE, FALSE);
-               gtk_widget_set_usize(completion_window, width,
-                                    gdk_screen_height () - y);
-       }
+       gtk_widget_set_uposition(window, x, y);
 
-       gtk_signal_connect(GTK_OBJECT(completion_window),
+       /* Resize window to fit initial (empty) address list */
+       gtk_widget_size_request( clist, &r );
+       gtk_widget_set_usize( window, width, r.height );
+       gtk_widget_show_all( window );
+       gtk_widget_size_request( clist, &r );
+
+       /* Setup handlers */
+       gtk_signal_connect(GTK_OBJECT(clist), "select_row",
+                          GTK_SIGNAL_FUNC(completion_window_select_row),
+                          _compWindow_ );
+       gtk_signal_connect(GTK_OBJECT(window),
                           "button-press-event",
                           GTK_SIGNAL_FUNC(completion_window_button_press),
-                          &completion_window);
-       gtk_signal_connect(GTK_OBJECT(completion_window),
+                          _compWindow_ );
+       gtk_signal_connect(GTK_OBJECT(window),
                           "key-press-event",
                           GTK_SIGNAL_FUNC(completion_window_key_press),
-                          &completion_window);
-       gdk_pointer_grab(completion_window->window, TRUE,
+                          _compWindow_ );
+       gdk_pointer_grab(window->window, TRUE,
                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
                         GDK_BUTTON_RELEASE_MASK,
                         NULL, NULL, GDK_CURRENT_TIME);
-       gtk_grab_add(completion_window);
+       gtk_grab_add( window );
 
        /* this gets rid of the irritating focus rectangle that doesn't
         * follow the selection */
        GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS);
-       gtk_clist_select_row(GTK_CLIST(clist), 1, 0);
-}
 
+       /* Add first entry into address list */
+       searchTerm = g_strdup( _compWindow_->searchTerm );
+       addrcompl_add_entry( _compWindow_, searchTerm );
+       gtk_clist_select_row(GTK_CLIST(clist), 0, 0);
 
-/* row selection sends completed address to entry.
- * note: event is NULL if selected by anything else than a mouse button. */
+       g_free( searchTerm );
+}
+
+/**
+ * Respond to select row event in clist object. selection sends completed
+ * address to entry. Note: event is NULL if selected by anything else than a
+ * mouse button.
+ * \param widget   Window object.
+ * \param event    Event.
+ * \param compWind Reference to completion window.
+ */
 static void completion_window_select_row(GtkCList *clist, gint row, gint col,
                                         GdkEvent *event,
-                                        GtkWidget **completion_window)
+                                        CompletionWindow *compWin )
 {
        GtkEntry *entry;
 
-       g_return_if_fail(completion_window != NULL);
-       g_return_if_fail(*completion_window != NULL);
+       g_return_if_fail(compWin != NULL);
 
-       entry = GTK_ENTRY(gtk_object_get_data(GTK_OBJECT(*completion_window),
-                                             WINDOW_DATA_COMPL_ENTRY));
+       entry = GTK_ENTRY(compWin->entry);
        g_return_if_fail(entry != NULL);
 
-       completion_window_apply_selection(clist, entry);
-
+       /* Don't update unless user actually selects ! */
        if (!event || event->type != GDK_BUTTON_RELEASE)
                return;
 
-       clear_completion_cache();
-       gtk_widget_destroy(*completion_window);
-       *completion_window = NULL;
+       /* User selected address by releasing the mouse in drop-down list*/
+       completion_window_apply_selection( clist, entry );
+
+       addrcompl_destroy_window( _compWindow_ );
 }
 
-/* completion_window_button_press() - check is mouse click is anywhere
- * else (not in the completion window). in that case the completion
- * window is destroyed, and the original prefix is restored */
+/**
+ * Respond to button press in completion window. Check if mouse click is
+ * anywhere outside the completion window. In that case the completion
+ * window is destroyed, and the original searchTerm is restored.
+ *
+ * \param widget   Window object.
+ * \param event    Event.
+ * \param compWin  Reference to completion window.
+ */
 static gboolean completion_window_button_press(GtkWidget *widget,
                                               GdkEventButton *event,
-                                              GtkWidget **completion_window)
+                                              CompletionWindow *compWin )
+
 {
        GtkWidget *event_widget, *entry;
-       gchar *prefix;
+       gchar *searchTerm;
        gint cursor_pos;
        gboolean restore = TRUE;
 
-       g_return_val_if_fail(completion_window != NULL, FALSE);
-       g_return_val_if_fail(*completion_window != NULL, FALSE);
+       g_return_val_if_fail(compWin != NULL, FALSE);
 
-       entry = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window),
-                                              WINDOW_DATA_COMPL_ENTRY));
+       entry = compWin->entry;
        g_return_val_if_fail(entry != NULL, FALSE);
 
+       /* Test where mouse was clicked */
        event_widget = gtk_get_event_widget((GdkEvent *)event);
        if (event_widget != widget) {
                while (event_widget) {
@@ -860,41 +719,42 @@ static gboolean completion_window_button_press(GtkWidget *widget,
                                restore = FALSE;
                                break;
                        }
-                   event_widget = event_widget->parent;
+                       event_widget = event_widget->parent;
                }
        }
 
        if (restore) {
-               prefix = get_complete_address(0);
+               /* Clicked outside of completion window - restore */
+               searchTerm = _compWindow_->searchTerm;
                g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
-               replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
-               g_free(prefix);
+               replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos);
        }
 
-       clear_completion_cache();
-       gtk_widget_destroy(*completion_window);
-       *completion_window = NULL;
+       addrcompl_destroy_window( _compWindow_ );
 
        return TRUE;
 }
 
+/**
+ * Respond to key press in completion window.
+ * \param widget   Window object.
+ * \param event    Event.
+ * \param compWind Reference to completion window.
+ */
 static gboolean completion_window_key_press(GtkWidget *widget,
                                            GdkEventKey *event,
-                                           GtkWidget **completion_window)
+                                           CompletionWindow *compWin )
 {
        GdkEventKey tmp_event;
        GtkWidget *entry;
-       gchar *prefix;
+       gchar *searchTerm;
        gint cursor_pos;
        GtkWidget *clist;
 
-       g_return_val_if_fail(completion_window != NULL, FALSE);
-       g_return_val_if_fail(*completion_window != NULL, FALSE);
+       g_return_val_if_fail(compWin != NULL, FALSE);
 
-       entry = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window),
-                                              WINDOW_DATA_COMPL_ENTRY));
-       clist = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window),
-                                              WINDOW_DATA_COMPL_CLIST));
+       entry = compWin->entry;
+       clist = compWin->clist;
        g_return_val_if_fail(entry != NULL, FALSE);
 
        /* allow keyboard navigation in the alternatives clist */
@@ -922,9 +782,14 @@ static gboolean completion_window_key_press(GtkWidget *widget,
 
        /* look for presses that accept the selection */
        if (event->keyval == GDK_Return || event->keyval == GDK_space) {
-               clear_completion_cache();
-               gtk_widget_destroy(*completion_window);
-               *completion_window = NULL;
+               /* User selected address with a key press */
+
+               /* Display selected address in entry field */           
+               completion_window_apply_selection(
+                       GTK_CLIST(clist), GTK_ENTRY(entry) );
+
+               /* Discard the window */
+               addrcompl_destroy_window( _compWindow_ );
                return FALSE;
        }
 
@@ -942,12 +807,10 @@ static gboolean completion_window_key_press(GtkWidget *widget,
                return FALSE;
        }
 
-       /* other key, let's restore the prefix (orignal text) */
-       prefix = get_complete_address(0);
+       /* some other key, let's restore the searchTerm (orignal text) */
+       searchTerm = _compWindow_->searchTerm;
        g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
-       replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
-       g_free(prefix);
-       clear_completion_cache();
+       replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos);
 
        /* make sure anything we typed comes in the edit box */
        tmp_event.type       = event->type;
@@ -961,8 +824,173 @@ static gboolean completion_window_key_press(GtkWidget *widget,
        gtk_widget_event(entry, (GdkEvent *)&tmp_event);
 
        /* and close the completion window */
-       gtk_widget_destroy(*completion_window);
-       *completion_window = NULL;
+       addrcompl_destroy_window( _compWindow_ );
 
        return TRUE;
 }
+
+/*
+ * ============================================================================
+ * Publically accessible functions.
+ * ============================================================================
+ */
+/**
+ * Start address completion operation.
+ */
+gint start_address_completion(void)
+{
+       if( ! _compWindow_ ) {
+               _compWindow_ = addrcompl_create_window();
+               _queryID_ = 0;
+       }
+       addressbook_read_all();
+       return 0;
+}
+
+/**
+ * complete_address() - tries to complete an addres, and returns the
+ * number of addresses found. use get_complete_address() to get one.
+ * returns zero if no match was found, otherwise the number of addresses,
+ * with the original searchTerm at index 0.
+ * TODO: move out of here. This should be used for retrieving an address
+ * without completion.
+ */
+guint complete_address(const gchar *str)
+{
+       g_return_val_if_fail(str != NULL, 0);
+
+       /* Xstrdup_a(d, str, return 0); */
+
+       /* Attempt completion */
+       /* printf( "addrcompl: Start search...\n" ); */
+       /* addressbook_start_search( 0, str, NULL, address_completion_callback ); */
+       /* printf( "addrcompl: Start search...done\n" ); */
+       /* Existing code is below */
+
+       return 0;
+}
+
+/**
+ * Returns a complete address. TODO: remove
+ * get_complete_address() - returns a complete address. the returned
+ * string should be freed 
+ */
+gchar *get_complete_address(gint index)
+{
+       return NULL;
+}
+
+/**
+ * Terminate addess completion.
+ */
+gint end_address_completion(void)
+{
+       return 0;
+}
+
+/**
+ * Start address completion. Should be called when creating the main window
+ * containing address completion entries. This originally cleared the cache.
+ * Function no longer required?
+ */
+void address_completion_start(GtkWidget *mainwindow)
+{
+       start_address_completion();
+}
+
+/**
+ * Need unique data to make unregistering signal handler possible for the auto
+ * completed entry.
+ */
+#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
+
+/**
+ * Register specified entry widget for address completion.
+ * \param entry Address entry field.
+ */
+void address_completion_register_entry(GtkEntry *entry)
+{
+       g_return_if_fail(entry != NULL);
+       g_return_if_fail(GTK_IS_ENTRY(entry));
+
+       /* add hooked property */
+       gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
+
+       /* add keypress event */
+       gtk_signal_connect_full(GTK_OBJECT(entry), "key_press_event",
+                               GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
+                               NULL,
+                               COMPLETION_UNIQUE_DATA,
+                               NULL,
+                               0,
+                               0); /* magic */
+}
+
+/**
+ * Unregister specified entry widget from address completion operations.
+ * \param entry Address entry field.
+ */
+void address_completion_unregister_entry(GtkEntry *entry)
+{
+       GtkObject *entry_obj;
+
+       g_return_if_fail(entry != NULL);
+       g_return_if_fail(GTK_IS_ENTRY(entry));
+
+       entry_obj = gtk_object_get_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
+       g_return_if_fail(entry_obj);
+       g_return_if_fail(entry_obj == GTK_OBJECT(entry));
+
+       /* has the hooked property? */
+       gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
+
+       /* remove the hook */
+       gtk_signal_disconnect_by_func(GTK_OBJECT(entry), 
+               GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
+               COMPLETION_UNIQUE_DATA);
+}
+
+/**
+ * End address completion. Should be called when main window with address
+ * completion entries terminates. NOTE: this function assumes that it is
+ * called upon destruction of the window.
+ */
+void address_completion_end(GtkWidget *mainwindow)
+{
+       /* if address_completion_end() is really called on closing the window,
+        * we don't need to unregister the set_focus_cb */
+       end_address_completion();
+}
+
+/**
+ * Setup completion object.
+ */
+void addrcompl_initialize( void ) {
+       printf( "addrcompl_initialize...\n" );
+       if( ! _compWindow_ ) {
+               _compWindow_ = addrcompl_create_window();
+       }
+       _queryID_ = 0;
+       _completionIdleID_ = 0;
+       printf( "addrcompl_initialize...done\n" );
+}
+
+/**
+ * Teardown completion object.
+ */
+void addrcompl_teardown( void ) {
+       printf( "addrcompl_teardown...\n" );
+       addrcompl_free_window( _compWindow_ );
+       _compWindow_ = NULL;
+       if( _displayQueue_ ) {
+               g_list_free( _displayQueue_ );
+       }
+       _displayQueue_ = NULL;
+       _completionIdleID_ = 0;
+       printf( "addrcompl_teardown...done\n" );
+}
+
+/*
+ * End of Source.
+ */
+
index b6ba7c6..fcb65a4 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
  * 
- * Copyright (c) 2000 by Alfons Hoogervorst <alfons@proteus.demon.nl>
+ * Copyright (c) 2000-2003 by Alfons Hoogervorst <alfons@proteus.demon.nl>
  *
  * 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
 #define __ADDR_COMPL_H__
 
 gint start_address_completion          (void);
-gint invalidate_address_completion     (void);
-
-guint complete_address                 (const gchar    *str);
-
-gchar *get_address_from_edit           (GtkEntry       *entry,
-                                        gint           *start_pos);
-void replace_address_in_edit           (GtkEntry       *entry,
-                                        const gchar    *newtext,
-                                        gint            start_pos);
-
-gchar *get_complete_address            (gint            index);
-
-gchar *get_next_complete_address       (void);
-gchar *get_prev_complete_address       (void);
-guint get_completion_count             (void);
-
-gboolean is_completion_pending         (void);
-
-void clear_completion_cache            (void);
-
+guint complete_address                 (const gchar *str);
+gchar *get_complete_address            (gint        index);
 gint end_address_completion            (void);
 
 /* ui functions */
+void address_completion_start          (GtkWidget *mainwindow);
+void address_completion_register_entry (GtkEntry  *entry);
+void address_completion_unregister_entry(GtkEntry  *entry);
+void address_completion_end            (GtkWidget *mainwindow);
 
-void address_completion_start  (GtkWidget *mainwindow);
-void address_completion_register_entry (GtkEntry *entry);
-void address_completion_unregister_entry (GtkEntry *entry);
-void address_completion_end    (GtkWidget *mainwindow);
+void addrcompl_initialize      ( void );
+void addrcompl_teardown                ( void );
 
 #endif /* __ADDR_COMPL_H__ */
index 8df8276..e81625c 100644 (file)
@@ -61,6 +61,10 @@ AddressBookFile *addrbook_create_book()
        book->tempList = NULL;
        book->tempHash = NULL;
        book->addressCache->modified = TRUE;
+
+       /* We want to use an address completion index */
+       addrcache_use_index( book->addressCache, TRUE );
+
        return book;
 }
 
@@ -188,7 +192,6 @@ void addrbook_free_book(AddressBookFile *book)
        g_return_if_fail(book != NULL);
 
        /* Clear cache */
-       addrcache_clear(book->addressCache);
        addrcache_free(book->addressCache);
 
        /* Free up internal objects */
@@ -233,9 +236,9 @@ void addrbook_print_book(AddressBookFile *book, FILE *stream)
        g_return_if_fail(book != NULL);
 
        fprintf(stream, "AddressBook:\n");
-       fprintf(stream, "\t  path : '%s'\n", book->path);
-       fprintf(stream, "\t  file : '%s'\n", book->fileName);
-       fprintf(stream, "\tstatus : %d\n",   book->retVal );
+       fprintf(stream, "\tpath : '%s'\n", book->path);
+       fprintf(stream, "\tfile : '%s'\n", book->fileName);
+       fprintf(stream, "\tstatus : %d\n", book->retVal );
        addrcache_print(book->addressCache, stream);
 }
 
@@ -875,6 +878,9 @@ gint addrbook_read_data(AddressBookFile *book)
                book->addressCache->modified = FALSE;
                book->addressCache->dataRead = TRUE;
                addrcache_set_dirty(book->addressCache, FALSE);
+
+               /* Build address completion index */
+               addrcache_build_index( book->addressCache );
        }
        return book->retVal;
 }
@@ -1965,27 +1971,36 @@ GList *addrbook_get_all_persons(AddressBookFile *book)
        return addrcache_get_all_persons(book->addressCache);
 }
 
-/* Add person and address data to address book.
-   Enter: book      Address book.
-          folder    Folder where to add person, or NULL for root folder.
-          name      Common name.
-          address   EMail address.
-          remarks   Remarks.
-   Return: Person added. Do not *NOT* to use the addrbook_free_xxx() functions...
-   this will destroy the address book data */
+/**
+ * Add person and address data to address book.
+ * \param  book    Address book.
+ * \param  folder  Folder where to add person, or NULL for root folder.
+ * \param  name    Common name.
+ * \param  address EMail address.
+ * \param  remarks Remarks.
+ * \return Person added. Do not <b>*NOT*</b> to use the
+ *         <code>addrbook_free_xxx()</code> functions... this will destroy
+ *         the address book data.
+ */
 ItemPerson *addrbook_add_contact(AddressBookFile *book, ItemFolder *folder, 
                                 const gchar *name,const gchar *address, 
                                 const gchar *remarks)
 {
+       ItemPerson *person;
+
        g_return_val_if_fail(book != NULL, NULL);
-       return addrcache_add_contact(book->addressCache, folder, name, address, 
-                                    remarks);
+       person = addrcache_add_contact(
+                       book->addressCache, folder, name, address, remarks );
+       addrcache_invalidate( book->addressCache );
+       return person;
 }
 
-/* Return file name for next address book file.
-   Enter:  book Address book.
-   Return: File name, or NULL if could not create. This should be g_free()
-           when done */
+/**
+ * Return file name for next address book file.
+ * \param  book Address book.
+ * \return File name, or <i>NULL</i> if could not create. This should be
+ *         <code>g_free()</code> when done.
+ */
 gchar *addrbook_guess_next_file(AddressBookFile *book)
 {
        gchar *newFile = NULL;
@@ -2000,3 +2015,18 @@ gchar *addrbook_guess_next_file(AddressBookFile *book)
        fileList = NULL;
        return newFile;
 }
+
+/**
+ * Invalidate the address book data. This will cause index to be rebuilt.
+ * \param book Address book.
+ */
+void addrbook_invalidate( AddressBookFile *book ) {
+       g_return_if_fail( book != NULL );
+       addrcache_invalidate( book->addressCache );
+}
+
+/*
+* End of Source.
+*/
+
+
index 84d8cd3..deae50c 100644 (file)
@@ -115,5 +115,6 @@ ItemPerson *addrbook_add_contact    ( AddressBookFile *book, ItemFolder *folder,
                                          const gchar *remarks );
 
 gchar *addrbook_guess_next_file                ( AddressBookFile *book );
+void addrbook_invalidate               ( AddressBookFile *book );
 
 #endif /* __ADDRBOOK_H__ */
index 1edf82a..df273c5 100644 (file)
@@ -76,6 +76,9 @@ AddressCache *addrcache_create() {
        cache->rootFolder = addritem_create_item_folder();
        cache->rootFolder->isRoot = TRUE;
        ADDRITEM_PARENT(cache->rootFolder) = NULL;
+
+       cache->searchIndex = NULL;
+
        return cache;
 }
 
@@ -217,7 +220,9 @@ static void addrcache_free_all_folders( ItemFolder *parent ) {
 void addrcache_clear( AddressCache *cache ) {
        g_return_if_fail( cache != NULL );
 
-       /* printf( "...addrcache_clear :%s:\n", cache->name ); */
+       /* Clear completion index */
+       addrcindex_clear( cache->searchIndex );
+
        /* Free up folders and hash table */
        addrcache_free_all_folders( cache->rootFolder );
        addrcache_free_item_hash( cache->itemHash );
@@ -244,6 +249,10 @@ void addrcache_clear( AddressCache *cache ) {
 void addrcache_free( AddressCache *cache ) {
        g_return_if_fail( cache != NULL );
 
+       /* Free completion index */
+       addrcindex_free( cache->searchIndex );
+       cache->searchIndex = NULL;
+
        cache->dirtyFlag = FALSE;
        addrcache_free_all_folders( cache->rootFolder );
        addrcache_free_item_hash( cache->itemHash );
@@ -258,6 +267,7 @@ void addrcache_free( AddressCache *cache ) {
        cache->cacheID = NULL;
        g_free( cache->name );
        cache->name = NULL;
+
        g_free( cache );
 }
 
@@ -1364,6 +1374,84 @@ ItemPerson *addrcache_add_contact( AddressCache *cache, ItemFolder *folder, cons
        return person;
 }
 
+/**
+ * Clear address completion index.
+ * \param cache Cache.
+ */
+void addrcache_clear_index( AddressCache *cache ) {
+       g_return_if_fail( cache != NULL );
+       addrcindex_clear( cache->searchIndex );
+}
+
+/**
+ * Control creation of an address completion index.
+ * \param cache Cache.
+ * \param value Set to <i>TRUE</i> to create an index, or <i>FALSE</i> to
+ *              destroy index.
+ */
+void addrcache_use_index( AddressCache *cache, gboolean value ) {
+       g_return_if_fail( cache != NULL );
+
+       if( value ) {
+               if( cache->searchIndex ) {
+                       addrcindex_clear( cache->searchIndex );
+               }
+               else {
+                       cache->searchIndex = addrcindex_create();
+               }
+       }
+       else {
+               addrcindex_free( cache->searchIndex );
+               cache->searchIndex = NULL;
+       }
+}
+
+/*
+ * Load completion callback function.
+ */
+static void addrcache_load_index_cb( gpointer key, gpointer value, gpointer data ) {
+       AddrItemObject *obj = ( AddrItemObject * ) value;
+
+       if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) {
+               ItemPerson *person = ( ItemPerson * ) obj;
+               AddrCacheIndex *index = data;
+               addrcindex_add_person( index, person );
+       }
+}
+
+/**
+ * Rebuild address completion index with all persons in cache.
+ * \param cache Cache.
+ */
+void addrcache_build_index( AddressCache *cache ) {
+       g_return_if_fail( cache != NULL );
+
+       if( cache->searchIndex == NULL ) return;
+
+       /* Clear index */
+       addrcindex_clear( cache->searchIndex );
+
+       /* Now load up */       
+       g_hash_table_foreach(
+               cache->itemHash, addrcache_load_index_cb, cache->searchIndex );
+       addrcindex_validate( cache->searchIndex );
+
+       /* addrcindex_print( cache->searchIndex, stdout ); */
+}
+
+/**
+ * Invalidate the address cache. This will cause index to be rebuilt.
+ * \param cache Cache.
+ */
+void addrcache_invalidate( AddressCache *cache ) {
+       g_return_if_fail( cache != NULL );
+
+       if( cache->searchIndex == NULL ) return;
+       addrcindex_invalidate( cache->searchIndex );
+}
+
 /*
 * End of Source.
 */
+
+
index 754e104..b7da168 100644 (file)
@@ -28,6 +28,7 @@
 #include <stdio.h>
 #include <glib.h>
 #include "addritem.h"
+#include "addrcindex.h"
 
 /* Address cache */
 typedef struct _AddressCache AddressCache;
@@ -44,6 +45,7 @@ struct _AddressCache {
        gboolean   dirtyFlag;
        gboolean   accessFlag;
        gchar      *name;
+       AddrCacheIndex *searchIndex;
 };
 
 /* Function prototypes */
@@ -170,4 +172,10 @@ ItemPerson *addrcache_add_contact  ( AddressCache *cache,
                                          const gchar *address,
                                          const gchar *remarks ); 
 
+void addrcache_clear_index     ( AddressCache *cache );
+void addrcache_create_index    ( AddressCache *cache,
+                                 gboolean value );
+void addrcache_build_index     ( AddressCache *cache );
+void addrcache_invalidate      ( AddressCache *cache );
+
 #endif /* __ADDRCACHE_H__ */
diff --git a/src/addrcindex.c b/src/addrcindex.c
new file mode 100644 (file)
index 0000000..f16d436
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2002-2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions to maintain address completion index.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "mgutils.h"
+#include "addritem.h"
+#include "addrcindex.h"
+
+/*
+static gint _n_created = 0;
+static gint _n_freed   = 0;
+*/
+
+typedef struct {
+       gchar     *name;
+       ItemEMail *address;
+}
+AddrIndexEntry;
+
+static gchar *addrcindex_function( gpointer data ) {
+       return ( ( AddrIndexEntry * ) data )->name;
+}
+
+/*
+* Create new completion index.
+*/
+AddrCacheIndex *addrcindex_create( void ) {
+       AddrCacheIndex *index;
+
+       /*
+       ++_n_created;
+       printf( "addrcindex_create/1/%d\n", _n_created );
+       */
+       index = g_new0( AddrCacheIndex, 1 );
+       index->completion = g_completion_new( addrcindex_function );
+       index->addressList = NULL;
+       index->invalid = TRUE;
+
+       return index;
+}
+
+/*
+* Clear the completion index.
+*/
+void addrcindex_clear( AddrCacheIndex *index ) {
+       if( index ) {
+               /* Clear completion index */
+               g_completion_clear_items( index->completion );
+
+               /* Clear address list */        
+               g_list_free( index->addressList );
+               index->addressList = NULL;
+               index->invalid = TRUE;
+       }
+}
+
+/*
+* Free completion index.
+*/
+void addrcindex_free( AddrCacheIndex *index ) {
+       if( index ) {
+               /*
+               ++_n_freed;
+               printf( "addrcindex_free/2/%d\n", _n_freed );
+               */
+               /* Clear out */
+               addrcindex_clear( index );
+
+               /* Free up */
+               g_completion_free( index->completion );
+               index->completion = NULL;
+               index->invalid = FALSE;
+
+               g_free( index );
+       }
+}
+
+/**
+ * Mark index as invalid. Will need to be rebuilt.
+ * \param index Address completion index.
+ */
+void addrcindex_invalidate( AddrCacheIndex *index ) {
+       g_return_if_fail( index != NULL );
+       index->invalid = TRUE;
+}
+
+/**
+ * Mark index as valid.
+ * \param index Address completion index.
+ */
+void addrcindex_validate( AddrCacheIndex *index ) {
+       g_return_if_fail( index != NULL );
+       index->invalid = FALSE;
+}
+
+/*
+ * Add completion entry to index.
+ * Enter: index Index.
+ *        name  Name.
+ *        email EMail entry to add.
+ */
+void addrcindex_add_entry(
+       AddrCacheIndex *index, gchar *name, ItemEMail *email )
+{
+       AddrIndexEntry *entry;
+
+       entry = g_new0( AddrIndexEntry, 1 );
+       entry->name = g_strdup( name );
+       entry->address = email;
+       g_strdown( entry->name );
+       index->addressList = g_list_append( index->addressList, entry );
+}
+
+/*
+* Add an email entry into index. The index will also include all name fields
+* for the person.
+* 
+* Add address into index.
+* Enter: index Index.
+*        email E-Mail to add.
+*/
+void addrcindex_add_email( AddrCacheIndex *index, ItemEMail *email ) {
+       ItemPerson *person;
+       gchar *name;
+       GSList *uniqName;
+       GSList *node;
+       gboolean flag;
+
+       g_return_if_fail( index != NULL );
+       g_return_if_fail( email != NULL );
+
+       flag = FALSE;
+       uniqName = NULL;
+       name = ADDRITEM_NAME( email );
+       if( name != NULL ) {
+               if( strlen( name ) > 0 ) {
+                       uniqName = g_slist_append( uniqName, name );
+               }
+       }
+       name = email->address;
+       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+               uniqName = g_slist_append( uniqName, name );
+       }
+       if( name ) {
+               if( strlen( name ) > 0 ) flag = TRUE;
+       }
+
+       /* Bail if no email address */
+       if( ! flag ) {
+               g_slist_free( uniqName );
+               return;
+       }
+
+       person = ( ItemPerson * ) ADDRITEM_PARENT( email );
+       if( person != NULL ) {
+               name = ADDRITEM_NAME( person );
+               if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+                       uniqName = g_slist_append( uniqName, name );
+               }
+
+               name = person->nickName;
+               if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+                       uniqName = g_slist_append( uniqName, name );
+               }
+
+               name = person->firstName;
+               if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+                       uniqName = g_slist_append( uniqName, name );
+               }
+
+               name = person->lastName;
+               if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+                       uniqName = g_slist_append( uniqName, name );
+               }
+       }
+
+       /* Create a completion entry for each unique name */
+       node = uniqName;
+       while( node ) {
+               addrcindex_add_entry( index, node->data, email );
+               node = g_slist_next( node );
+       }
+       g_slist_free( uniqName );
+
+}
+
+/*
+ * Process email address entry, checking for unique alias and address. If the
+ * address field is empty, no entries will be generated.
+ * Enter: index Index.
+ *        uniqName List of unique names to examine.
+ *        email    EMail address item to process.
+ * Return: List of entries from email object to add to index.
+ */
+static GSList *addrcindex_proc_mail(
+       AddrCacheIndex *index, GSList *uniqName, ItemEMail *email )
+{
+       GSList *list;
+       gchar *name;
+
+       /* Test for address */
+       list = NULL;
+       name = email->address;
+       if( name ) {
+               if( strlen( name ) > 0 ) {
+                       /* Address was supplied */
+                       /* Append alias if unique */
+                       name = ADDRITEM_NAME( email );
+                       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+                               list = g_slist_append( list, name );
+                       }
+                       /* Then append the address if unique */
+                       /* Note is possible that the address has already */
+                       /* been entered into one of the name fields. */
+                       if( mgu_slist_test_unq_nc( uniqName, email->address ) ) {
+                               list = g_slist_append( list, email->address );
+                       }
+               }
+       }
+       return list;
+}
+
+/*
+* Add person's address entries into index. Each email address is processed.
+* If the address field has been supplied, entries will be made. The index
+* will include the address, alias and all name fields for the person.
+* 
+* Enter: index  Index.
+*        person Person to add.
+*/
+void addrcindex_add_person( AddrCacheIndex *index, ItemPerson *person ) {
+       gchar *name;
+       GSList *uniqName;
+       GSList *node;
+       GSList *listMail;
+       GList  *listEMail;
+       ItemEMail *email;
+
+       g_return_if_fail( index != NULL );
+       g_return_if_fail( person != NULL );
+
+       /* Build list of all unique names in person's name fields */
+       uniqName = NULL;
+       name = ADDRITEM_NAME( person );
+       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+               uniqName = g_slist_append( uniqName, name );
+       }
+
+       name = person->nickName;
+       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+               uniqName = g_slist_append( uniqName, name );
+       }
+
+       name = person->firstName;
+       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+               uniqName = g_slist_append( uniqName, name );
+       }
+
+       name = person->lastName;
+       if( mgu_slist_test_unq_nc( uniqName, name ) ) {
+               uniqName = g_slist_append( uniqName, name );
+       }
+
+       /* Process each email address entry */
+       listEMail = person->listEMail;
+       while( listEMail ) {
+               email = listEMail->data;
+               listMail = addrcindex_proc_mail( index, uniqName, email );
+               if( listMail ) {
+                       /* Create a completion entry for the address item */
+                       node = listMail;
+                       while( node ) {
+                               /* printf( "\tname-m::%s::\n", node->data ); */
+                               addrcindex_add_entry( index, node->data, email );
+                               node = g_slist_next( node );
+                       }
+                       /* ... and all person's name entries */
+                       node = uniqName;
+                       while( node ) {
+                               /* printf( "\tname-p::%s::\n", node->data ); */
+                               addrcindex_add_entry( index, node->data, email );
+                               node = g_slist_next( node );
+                       }
+                       g_slist_free( listMail );
+               }
+               listEMail = g_list_next( listEMail );
+       }
+
+       /* Free up the list */
+       g_slist_free( uniqName );
+}
+
+/*
+* Print index to stream.
+* Enter: index  Index.
+*        stream Output stream.
+*/
+void addrcindex_print( AddrCacheIndex *index, FILE *stream ) {
+       GList *node;
+       AddrIndexEntry *entry;
+       ItemEMail *email;
+
+       g_return_if_fail( index != NULL );
+       fprintf( stream, "AddressSearchIndex:\n" );
+       node = index->addressList;
+       while( node ) {
+               entry = node->data;
+               email = entry->address;
+               fprintf( stream, "\tname: '%s'\t'%s'\n", entry->name, email->address );
+               node = g_list_next( node );
+       }
+}
+
+/*
+* Perform search for specified search term.
+* Enter: index  Completion index.
+*        search Search string.
+* Return: List of references to ItemEMail objects meeting search criteria. The
+*         list should be g_list_free() when no longer required.
+*/
+GList *addrcindex_search( AddrCacheIndex *index, const gchar *search ) {
+       AddrIndexEntry *entry;
+       gchar *prefix;
+       GList *list;
+       GList *node;
+       GList *listEMail;
+
+       g_return_if_fail( index != NULL );
+       g_return_if_fail( search != NULL );
+
+       listEMail = NULL;
+       if( index->addressList != NULL ) { 
+               /* Add items to list */
+               g_completion_add_items( index->completion, index->addressList );
+
+               /* Perform the search */
+               prefix = g_strdup( search );
+               g_strdown( prefix );
+               list = g_completion_complete( index->completion, prefix, NULL );
+               g_free( prefix );
+
+               /* Build list of unique EMail objects */
+               node = list;
+               while( node ) {
+                       entry = node->data;
+                       /* printf( "\tname ::%s::\n", entry->name ); */
+                       if( NULL == g_list_find( listEMail, entry->address ) ) {
+                               listEMail = g_list_append(
+                                               listEMail, entry->address );
+                       }
+                       node = g_list_next( node );
+               }
+       }
+
+       return listEMail;
+}
+
+/*
+* End of Source.
+*/
diff --git a/src/addrcindex.h b/src/addrcindex.h
new file mode 100644 (file)
index 0000000..8ede3b8
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2002-2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions to maintain address completion index.
+ */
+
+#ifndef __ADDRCINDEX_H__
+#define __ADDRCINDEX_H__
+
+#include <glib.h>
+#include <stdio.h>
+
+#include "addritem.h"
+
+/*
+ * Constants.
+ */
+
+/* Data structures */
+typedef struct {
+       GCompletion *completion;
+       GList       *addressList;
+       gboolean    invalid;
+}
+AddrCacheIndex;
+
+/* Function prototypes */
+AddrCacheIndex *addrcindex_create      ( void );
+void addrcindex_clear          ( AddrCacheIndex *index );
+void addrcindex_free           ( AddrCacheIndex *index );
+void addrcindex_invalidate     ( AddrCacheIndex *index );
+void addrcindex_validate       ( AddrCacheIndex *index );
+void addrcindex_add_entry      ( AddrCacheIndex *index,
+                                 gchar *name,
+                                 ItemEMail *email );
+void addrcindex_add_email      ( AddrCacheIndex *index, ItemEMail *email );
+void addrcindex_add_person     ( AddrCacheIndex *index, ItemPerson *person );
+void addrcindex_print          ( AddrCacheIndex *index, FILE *stream );
+GList *addrcindex_search       ( AddrCacheIndex *index, const gchar *search );
+
+#endif /* __ADDRCINDEX_H__ */
+
+/*
+* End of Source.
+*/
index 3f0189e..0901b40 100644 (file)
@@ -85,7 +85,7 @@
 
 #ifdef USE_LDAP
 #include <pthread.h>
-#include "syldap.h"
+#include "ldapserver.h"
 #include "editldap.h"
 
 #define ADDRESSBOOK_LDAP_BUSYMSG "Busy"
@@ -313,7 +313,7 @@ static void addressbook_folder_remove_node  (GtkCTree *clist,
                                                 GtkCTreeNode *node);
 
 #ifdef USE_LDAP
-static void addressbook_ldap_show_message      (SyldapServer *server);
+static void addressbook_ldap_show_message      ( LdapServer *server );
 #endif
 
 /* LUT's and IF stuff */
@@ -526,6 +526,7 @@ void addressbook_destroy() {
        if( _clipBoard_ != NULL ) {
                addrclip_free( _clipBoard_ );
        }
+       addrcompl_teardown();
        if( _addressIndex_ != NULL ) {
               addrindex_free_index( _addressIndex_ );
        }
@@ -2377,7 +2378,6 @@ static void addressbook_edit_address_cb( gpointer data, guint action, GtkWidget
                        person = ( ItemPerson * ) ADDRITEM_PARENT(email);
                        if( addressbook_edit_person( abf, NULL, person, TRUE ) == NULL ) return;
                        addressbook_folder_refresh_one_person( clist, person );
-                       invalidate_address_completion();
                        return;
                }
        }
@@ -2385,7 +2385,6 @@ static void addressbook_edit_address_cb( gpointer data, guint action, GtkWidget
                /* Edit person - basic page */
                ItemPerson *person = ( ItemPerson * ) obj;
                if( addressbook_edit_person( abf, NULL, person, FALSE ) == NULL ) return;
-               invalidate_address_completion();
                addressbook_folder_refresh_one_person( clist, person );
                return;
        }
@@ -2992,6 +2991,7 @@ void addressbook_read_file( void ) {
        }
 
        addrIndex = addrindex_create_index();
+       addrcompl_initialize();
 
        /* Use new address book index. */
        addrindex_set_file_path( addrIndex, get_rc_dir() );
@@ -3112,7 +3112,8 @@ static GtkCTreeNode *addressbook_node_add_group( GtkCTreeNode *node, AddressData
 * Return: Inserted node.
 */
 static GtkCTreeNode *addressbook_node_add_folder(
-               GtkCTreeNode *node, AddressDataSource *ds, ItemFolder *itemFolder, AddressObjectType otype )
+               GtkCTreeNode *node, AddressDataSource *ds,
+               ItemFolder *itemFolder, AddressObjectType otype )
 {
        GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
        GtkCTreeNode *newNode = NULL;
@@ -3144,8 +3145,9 @@ static GtkCTreeNode *addressbook_node_add_folder(
                newNode = gtk_ctree_insert_node( ctree, node, NULL, name, FOLDER_SPACING,
                                atci->iconXpm, atci->maskXpm, atci->iconXpm, atci->maskXpm,
                                atci->treeLeaf, atci->treeExpand );
-               gtk_ctree_node_set_row_data_full( ctree, newNode, adapter,
-                       addressbook_free_treenode );
+               if( newNode )
+                       gtk_ctree_node_set_row_data_full( ctree, newNode, adapter,
+                               addressbook_free_treenode );
        }
 
        listItems = itemFolder->listFolder;
@@ -3175,9 +3177,6 @@ void addressbook_export_to_file( void ) {
                if( _addressIndex_->retVal != MGU_SUCCESS ) {
                        addrindex_print_index( _addressIndex_, stdout );
                }
-
-               /* Notify address completion of new data */
-               invalidate_address_completion();
        }
 }
 
@@ -3313,43 +3312,36 @@ static void addressbook_new_ldap_cb( gpointer data, guint action, GtkWidget *wid
        }
 }
 
-static void addressbook_ldap_show_message( SyldapServer *svr ) {
+static void addressbook_ldap_show_message( LdapServer *svr ) {
        gchar *name;
        gchar *desc;
        *addressbook_msgbuf = '\0';
        if( svr ) {
-               name = syldap_get_name( svr );
-               if( svr->busyFlag ) {
+               name = ldapsvr_get_name( svr );
+               if( svr->retVal == MGU_SUCCESS ) {
                        g_snprintf( addressbook_msgbuf,
-                                   sizeof(addressbook_msgbuf), "%s: %s", name,
-                                   ADDRESSBOOK_LDAP_BUSYMSG );
+                                   sizeof(addressbook_msgbuf), "%s",
+                                   name );
                }
                else {
-                       if( svr->retVal == MGU_SUCCESS ) {
-                               g_snprintf( addressbook_msgbuf,
-                                           sizeof(addressbook_msgbuf), "%s",
-                                           name );
-                       }
-                       else {
-                               desc = addressbook_err2string(
-                                               _lutErrorsLDAP_, svr->retVal );
-                               g_snprintf( addressbook_msgbuf,
-                                           sizeof(addressbook_msgbuf),
-                                           "%s: %s", name, desc );
-                       }
+                       desc = addressbook_err2string(
+                                       _lutErrorsLDAP_, svr->retVal );
+                       g_snprintf( addressbook_msgbuf,
+                                   sizeof(addressbook_msgbuf),
+                                   "%s: %s", name, desc );
                }
        }
        addressbook_status_show( addressbook_msgbuf );
 }
 
-static void addressbook_ldap_show_results( SyldapServer *sls ) {
+static void addressbook_ldap_show_results( LdapServer *server ) {
        GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
        AddressObject *obj;
        AdapterDSource *ads = NULL;
        AddressDataSource *ds = NULL;
        AddressInterface *iface = NULL;
 
-       if( sls == NULL ) return;
+       if( server == NULL ) return;
        if( ! addrbook.treeSelected ) return;
        if( GTK_CTREE_ROW( addrbook.treeSelected )->level == 1 ) return;
 
@@ -3358,18 +3350,18 @@ static void addressbook_ldap_show_results( SyldapServer *sls ) {
        if( obj->type == ADDR_DATASOURCE ) {
                ads = ADAPTER_DSOURCE(obj);
                if( ads->subType == ADDR_LDAP ) {
-                       SyldapServer *server;
+                       LdapServer *ldapSvr;
 
                        ds = ads->dataSource;
                        if( ds == NULL ) return;
                        iface = ds->interface;
                        if( ! iface->haveLibrary ) return;
                        server = ds->rawDataSource;
-                       if( server == sls ) {
+                       if( ldapSvr == server ) {
                                /* Read from cache */
                                gtk_widget_show_all(addrbook.window);
                                addressbook_set_clist( obj );
-                               addressbook_ldap_show_message( sls );
+                               addressbook_ldap_show_message( server );
                                gtk_widget_show_all(addrbook.window);
                                gtk_entry_set_text( GTK_ENTRY(addrbook.entry), "" );
                        }
@@ -3377,24 +3369,27 @@ static void addressbook_ldap_show_results( SyldapServer *sls ) {
        }
 }
 
+static gint _idleID_ = 0;
+static gchar *_tempMessage_ = "Busy searching LDAP...";
+
 /*
  * LDAP idle function. This function is called during UI idle time while
  * an LDAP search is in progress.
  * Enter: data Reference to LDAP server object.
  */
 static void addressbook_ldap_idle( gpointer data ) {
-       SyldapServer *server;
-
-       server = ( SyldapServer * ) data;       
-       if( ! server->busyFlag ) {
-               /* Server has completed search - remove from idle list */
-               gtk_idle_remove( server->idleId );
+}
 
-               /* Process callback and free up the thread */
-               addressbook_ldap_show_results( server );
-               g_free( server->thread );
-               server->thread = NULL;
+/*
+ * LDAP search completion function.
+ */
+static void addressbook_ldap_idle_end( void ) {
+       /* Server has completed search - remove from idle list */
+       printf( "addressbook_ldap_idle_end... completed" );
+       if( _idleID_ != 0 ) {
+               gtk_idle_remove( _idleID_ );
        }
+       _idleID_ = 0;
 }
 
 /*
@@ -3405,21 +3400,26 @@ static void addressbook_ldap_idle( gpointer data ) {
 static void addressbook_ldap_lookup( AdapterDSource *ads, gchar *sLookup ) {
        AddressDataSource *ds = NULL;
        AddressInterface *iface = NULL;
-       SyldapServer *server;
+       LdapServer *server;
 
+       printf( "addressbook_ldap_lookup/Searching for '%s'\n", sLookup );
        ds = ads->dataSource;
        if( ds == NULL ) return;
        iface = ds->interface;
        if( ! iface->haveLibrary ) return;
        server = ds->rawDataSource;
        if( server ) {
-               syldap_cancel_read( server );
+               printf( "addressbook_ldap_lookup/Starting.../1\n" );
                if( *sLookup == '\0' || strlen( sLookup ) < 1 ) return;
-               syldap_set_search_value( server, sLookup );
-               server->idleId = gtk_idle_add(
-                       ( GtkFunction ) addressbook_ldap_idle, server );
-               syldap_read_data_th( server );
-               addressbook_ldap_show_message( server );
+
+               /* Setup a query */
+               printf( "addressbook_ldap_lookup/Starting.../2\n" );
+
+               /* Sit back and wait for something to happen */
+               _idleID_ = gtk_idle_add(
+                       ( GtkFunction ) addressbook_ldap_idle, NULL );
+               addrindex_search_ldap_noid( server, sLookup, addressbook_ldap_idle_end  );
+               addressbook_status_show( _tempMessage_ );
        }
 }
 #endif
@@ -3429,9 +3429,11 @@ static void addressbook_ldap_lookup( AdapterDSource *ads, gchar *sLookup ) {
  */
 static void addressbook_lup_clicked( GtkButton *button, gpointer data ) {
        GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
+       AddressObject *pobj;
        AddressObject *obj;
        AdapterDSource *ads = NULL;
        gchar *sLookup;
+       GtkCTreeNode *node = NULL, *parentNode = NULL;
 
        if( ! addrbook.treeSelected ) return;
        if( GTK_CTREE_ROW( addrbook.treeSelected )->level == 1 ) return;
@@ -3441,16 +3443,25 @@ static void addressbook_lup_clicked( GtkButton *button, gpointer data ) {
 
        sLookup = gtk_editable_get_chars( GTK_EDITABLE(addrbook.entry), 0, -1 );
        g_strchomp( sLookup );
+       /* printf( "addressbook_lup_clicked/Lookup: '%s'\n", sLookup ); */
 
        if( obj->type == ADDR_DATASOURCE ) {
+               /* printf( "I am a datasource\n" ); */
                ads = ADAPTER_DSOURCE(obj);
-#ifdef USE_LDAP
-               if( ads->subType == ADDR_LDAP ) {
-                       addressbook_ldap_lookup( ads, sLookup );
+       }
+       else {
+               /* printf( "Test my parent\n" ); */
+               parentNode = GTK_CTREE_ROW(addrbook.treeSelected)->parent;
+               obj = gtk_ctree_node_get_row_data( ctree, parentNode );
+               if( obj->type == ADDR_DATASOURCE ) {
+                       ads = ADAPTER_DSOURCE(obj);
                }
-#endif /* USE_LDAP */
        }
-
+#ifdef USE_LDAP
+       if( ads && ads->subType == ADDR_LDAP ) {
+               addressbook_ldap_lookup( ads, sLookup );
+       }
+#endif
        g_free( sLookup );
 }
 
@@ -3844,88 +3855,6 @@ gboolean addressbook_add_contact( const gchar *name, const gchar *address, const
        return TRUE;
 }
 
-/* **********************************************************************
-* Address completion support.
-* ***********************************************************************
-*/
-
-/*
-* This function is used by the address completion function to load
-* addresses.
-* Enter: callBackFunc Function to be called when an address is
-*                     to be loaded.
-* Return: TRUE if data loaded, FALSE if address index not loaded.
-*/
-gboolean addressbook_load_completion( gint (*callBackFunc) ( const gchar *, const gchar *, const gchar * ) ) {
-       AddressDataSource *ds;
-       GList *nodeIf, *nodeDS;
-       GList *listP, *nodeP;
-       GList *nodeM;
-       gchar *sName, *sAddress, *sAlias, *sFriendly;
-
-       debug_print( "addressbook_load_completion\n" );
-
-       if( _addressIndex_ == NULL ) return FALSE;
-
-       nodeIf = addrindex_get_interface_list( _addressIndex_ );
-       while( nodeIf ) {
-               AddressInterface *interface = nodeIf->data;
-               nodeDS = interface->listSource;
-               while( nodeDS ) {
-                       ds = nodeDS->data;
-
-                       /* Read address book */
-                       if( addrindex_ds_get_modify_flag( ds ) ) {
-                               addrindex_ds_read_data( ds );
-                       }
-
-                       if( ! addrindex_ds_get_read_flag( ds ) ) {
-                               addrindex_ds_read_data( ds );
-                       }
-
-                       /* Get all persons */
-                       listP = addrindex_ds_get_all_persons( ds );
-                       nodeP = listP;
-                       while( nodeP ) {
-                               ItemPerson *person = nodeP->data;
-                               nodeM = person->listEMail;
-
-                               /* Figure out name to use */
-                               sName = person->nickName;
-                               if( sName == NULL || *sName == '\0' ) {
-                                       sName = ADDRITEM_NAME(person);
-                               }
-
-                               /* Process each E-Mail address */
-                               while( nodeM ) {
-                                       ItemEMail *email = nodeM->data;
-                                       /* Have mail */
-                                       sFriendly = sName;
-                                       sAddress = email->address;
-                                       if( sAddress || *sAddress != '\0' ) {
-                                               sAlias = ADDRITEM_NAME(email);
-                                               if( sAlias && *sAlias != '\0' ) {
-                                                       sFriendly = sAlias;
-                                               }
-                                               ( callBackFunc ) ( sFriendly, sAddress, sName );
-                                       }
-
-                                       nodeM = g_list_next( nodeM );
-                               }
-                               nodeP = g_list_next( nodeP );
-                       }
-                       /* Free up the list */
-                       g_list_free( listP );
-
-                       nodeDS = g_list_next( nodeDS );
-               }
-               nodeIf = g_list_next( nodeIf );
-       }
-       debug_print( "addressbook_load_completion... done\n" );
-
-       return TRUE;
-}
-
 /* **********************************************************************
 * Address Import.
 * ***********************************************************************
@@ -3961,9 +3890,6 @@ static void addressbook_import_ldif_cb() {
                                                newNode );
                                        addrbook.treeSelected = newNode;
                                }
-
-                               /* Notify address completion */
-                               invalidate_address_completion();
                        }
                }
        }
@@ -3999,9 +3925,6 @@ static void addressbook_import_mutt_cb() {
                                                newNode );
                                        addrbook.treeSelected = newNode;
                                }
-
-                               /* Notify address completion */
-                               invalidate_address_completion();
                        }
                }
        }
@@ -4037,9 +3960,6 @@ static void addressbook_import_pine_cb() {
                                                newNode );
                                        addrbook.treeSelected = newNode;
                                }
-
-                               /* Notify address completion */
-                               invalidate_address_completion();
                        }
                }
        }
@@ -4076,9 +3996,6 @@ void addressbook_harvest(
                                                ADDRESS_OBJECT(ads) );
                        }
                }
-
-               /* Notify address completion */
-               invalidate_address_completion();
        }
 }
 
@@ -4109,4 +4026,116 @@ static void addressbook_export_html_cb( void ) {
 /*
 * End of Source.
 */
+/* **********************************************************************
+* Address completion and search support.
+* ***********************************************************************
+*/
+
+/**
+ * Setup search for specified search string.
+ * \param searchTerm Search string.
+ * \param target     Target data.
+ * \param callBack   Call back function.
+ * \return ID allotcated to this query, or 0 if none.
+ */
+gint addressbook_setup_search(
+       const gchar *searchTerm, const gpointer target,
+       AddrSearchCallbackFunc callback )
+{
+       if( _addressIndex_ == NULL ) {
+               printf( "address index not loaded\n" );
+               return 0;
+       }
+       return addrindex_setup_search( _addressIndex_, searchTerm, target, callback );
+}
+
+/**
+ * Perform the previously registered search.
+ * \param  queryID ID of search query to be executed.
+ * \return <i>TRUE</i> if search started successfully, or <i>FALSE</i> if
+ *         failed.
+ */
+gboolean addressbook_start_search( const gint queryID ) {
+       if( _addressIndex_ == NULL ) {
+               printf( "address index not loaded\n" );
+               return FALSE;
+       }
+       return addrindex_start_search( _addressIndex_, queryID );
+}
+
+/**
+ * Stop the previously registered search.
+ * \param queryID ID of search query to stop.
+ */
+void addressbook_stop_search( const gint queryID ){
+       if( _addressIndex_ == NULL ) {
+               /* printf( "address index not loaded\n" ); */
+               return;
+       }
+       addrindex_stop_search( _addressIndex_, queryID );
+}
+
+/**
+ * Read all address interfaces and data sources.
+ */
+void addressbook_read_all( void ) {
+       if( _addressIndex_ == NULL ) {
+               /* Load index file */
+               /* printf( "address index not loaded\n" ); */
+               /* addressbook_read_file(); */
+       }
+
+       if( ! addrindex_get_loaded( _addressIndex_ ) ) {
+               /* Read all address books */
+               addrindex_read_all( _addressIndex_ );
+       }
+       /*
+       else {
+               printf( "Address data already loaded!!!!!!!!!!!!!!!\n" );
+       }
+       */
+}
+
+/**
+ * Perform a simple search of all non-query type data sources for specified
+ * search term. If several entries are found, only the first item is
+ * returned. Interfaces that require a time-consuming "query" are ignored for
+ * this search.
+ *
+ * \param  searchTerm Search term to find. Typically an email address.
+ * \return Reference to a single E-Mail object that was found in the address
+ *         book, or <i>NULL</i> if nothing found. This should *NOT* be freed
+ *         when done.
+ */
+ItemEMail *addressbook_quick_search_single( const gchar *searchTerm ) {
+       if( _addressIndex_ == NULL ) {
+               /* printf( "address index not loaded\n" ); */
+               return NULL;
+       }
+       return addrindex_quick_search_single( _addressIndex_, searchTerm );
+}
+
+/**
+ * Perform a simple search of all non-query type data sources for specified
+ * search term. If several entries are found, only the first item is
+ * returned. Interfaces that require a time-consuming "query" are ignored for
+ * this search.
+ *
+ * \param  addrIndex  Address index object.
+ * \param  searchTerm Search term to find. Typically an email address.
+ * \return List of references to zero or mail E-Mail object that was found in
+ *         the address books, or <i>NULL</i> if nothing found. This list
+ *         *SHOULD* be freed when done.
+ */
+GList *addressbook_quick_search_list( const gchar *searchTerm )
+{
+       if( _addressIndex_ == NULL ) {
+               printf( "address index not loaded\n" );
+               return NULL;
+       }
+       return addrindex_quick_search_list( _addressIndex_, searchTerm );
+}
+/*
+* End of Source.
+*/
 
index f5ee5cd..7e6d9c1 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999,2000 Hiroyuki Yamamoto
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
  *
  * 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
@@ -24,6 +24,8 @@
 #include <gtk/gtkwidget.h>
 
 #include "compose.h"
+#include "addrquery.h"
+#include "addritem.h"
 
 void addressbook_open                  (Compose        *target);
 void addressbook_set_target_compose    (Compose        *target);
@@ -38,8 +40,6 @@ gboolean addressbook_add_contact      ( const gchar   *name,
                                          const gchar   *address,
                                          const gchar   *remarks );
 
-gboolean addressbook_load_completion   ( gint (*callBackFunc) ( const gchar *, const gchar *, const gchar * ) );
-
 void addressbook_gather                        ( FolderItem *folderItem,
                                          gboolean sourceInd,
                                          GList *msgList );
@@ -47,4 +47,14 @@ void addressbook_harvest             (FolderItem     *folderItem,
                                         gboolean sourceInd,
                                         GList *msgList);
 
+gint addressbook_setup_search          ( const gchar *searchTerm,
+                                         const gpointer target,
+                                         AddrSearchCallbackFunc callback );
+gboolean addressbook_start_search      ( const gint queryID );
+void addressbook_stop_search           ( const gint queryID );
+
+void addressbook_read_all              ( void );
+GList *addressbook_quick_search_list   ( const gchar *searchTerm );
+ItemEMail *addressbook_quick_search_single( const gchar *searchTerm );
+
 #endif /* __ADDRESSBOOK_H__ */
index f2dd845..7de4fda 100644 (file)
@@ -36,6 +36,7 @@
 #include "addrbook.h"
 #include "addrindex.h"
 #include "xml.h"
+#include "addrquery.h"
 
 #ifndef DEV_STANDALONE
 #include "prefs_gtk.h"
@@ -49,7 +50,9 @@
 #endif
 
 #ifdef USE_LDAP
-#include "syldap.h"
+#include "ldapserver.h"
+#include "ldapctrl.h"
+#include "ldapquery.h"
 #endif
 
 #define TAG_ADDRESS_INDEX    "addressbook"
 #define ATTAG_LDAP_CRITERIA   "criteria"
 #define ATTAG_LDAP_MAX_ENTRY  "max-entry"
 #define ATTAG_LDAP_TIMEOUT    "timeout"
+#define ATTAG_LDAP_MAX_AGE    "max-age"
+#define ATTAG_LDAP_DYN_SEARCH "dyn-search"
+
+#define ELTAG_LDAP_ATTR_SRCH  "attribute"
+#define ATTAG_LDAP_ATTR_NAME  "name"
+
+/* New attributes */
+#define ATTAG_LDAP_DEFAULT    "default"
 
 #if 0
 N_("Common address")
@@ -104,6 +115,9 @@ N_("Personal address")
 #define DISP_OLD_COMMON       _("Common address")
 #define DISP_OLD_PERSONAL     _("Personal address")
 
+/*
+ * Define attribute name-value pair.
+ */
 typedef struct _AddressIfAttr AddressIfAttrib;
 struct _AddressIfAttr {
        gchar *name;
@@ -111,9 +125,27 @@ struct _AddressIfAttr {
 };
 
 /*
-* Build interface with default values.
+ * Define DOM fragment.
+ */
+typedef struct _AddressIfFrag AddressIfFragment;
+struct _AddressIfFrag {
+       gchar *name;
+       GList *children;
+       GList *attributes;
+};
+
+/**
+ * Build interface with default values.
+ *
+ * \param type Interface type.
+ * \param name Interface name.
+ * \param tagIf XML tag name for interface in address index file.
+ * \param tagDS XML tag name for datasource in address index file.
+ * \return Address interface object.
 */
-static AddressInterface *addrindex_create_interface( gint type, gchar *name, gchar *tagIf, gchar *tagDS ) {
+static AddressInterface *addrindex_create_interface(
+               gint type, gchar *name, gchar *tagIf, gchar *tagDS )
+{
        AddressInterface *iface = g_new0( AddressInterface, 1 );
 
        ADDRITEM_TYPE(iface) = ITEMTYPE_INTERFACE;
@@ -129,6 +161,8 @@ static AddressInterface *addrindex_create_interface( gint type, gchar *name, gch
        iface->haveLibrary = TRUE;
        iface->useInterface = TRUE;
        iface->readOnly      = TRUE;
+
+       /* Set callbacks to NULL values - override for each interface */
        iface->getAccessFlag = NULL;
        iface->getModifyFlag = NULL;
        iface->getReadFlag   = NULL;
@@ -141,16 +175,27 @@ static AddressInterface *addrindex_create_interface( gint type, gchar *name, gch
        iface->getAllGroups  = NULL;
        iface->getName       = NULL;
        iface->listSource = NULL;
+
+       /* Search stuff */
+       iface->externalQuery = FALSE;
+       iface->searchOrder = 0;         /* Ignored */
+       iface->startSearch = NULL;
+       iface->stopSearch = NULL;
+
        return iface;
 }
 
-/*
-* Build table of interfaces.
-*/
+/**
+ * Build table of of all address book interfaces.
+ * \param addrIndex Address index object.
+ */
 static void addrindex_build_if_list( AddressIndex *addrIndex ) {
        AddressInterface *iface;
 
-       iface = addrindex_create_interface( ADDR_IF_BOOK, "Address Book", TAG_IF_ADDRESS_BOOK, TAG_DS_ADDRESS_BOOK );
+       /* Create intrinsic XML address book interface */
+       iface = addrindex_create_interface(
+                       ADDR_IF_BOOK, "Address Book", TAG_IF_ADDRESS_BOOK,
+                       TAG_DS_ADDRESS_BOOK );
        iface->readOnly      = FALSE;
        iface->getModifyFlag = ( void * ) addrbook_get_modified;
        iface->getAccessFlag = ( void * ) addrbook_get_accessed;
@@ -163,10 +208,16 @@ static void addrindex_build_if_list( AddressIndex *addrIndex ) {
        iface->getAllPersons = ( void * ) addrbook_get_all_persons;
        iface->getName       = ( void * ) addrbook_get_name;
        iface->setAccessFlag = ( void * ) addrbook_set_accessed;
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       iface->searchOrder   = 2;
+
+       /* Add to list of interfaces in address book */ 
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
-       iface = addrindex_create_interface( ADDR_IF_VCARD, "vCard", TAG_IF_VCARD, TAG_DS_VCARD );
+       /* Create vCard interface */
+       iface = addrindex_create_interface(
+                       ADDR_IF_VCARD, "vCard", TAG_IF_VCARD, TAG_DS_VCARD );
        iface->getModifyFlag = ( void * ) vcard_get_modified;
        iface->getAccessFlag = ( void * ) vcard_get_accessed;
        iface->getReadFlag   = ( void * ) vcard_get_read_flag;
@@ -178,10 +229,15 @@ static void addrindex_build_if_list( AddressIndex *addrIndex ) {
        iface->getAllPersons = ( void * ) vcard_get_all_persons;
        iface->getName       = ( void * ) vcard_get_name;
        iface->setAccessFlag = ( void * ) vcard_set_accessed;
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       iface->searchOrder   = 3;
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
-       iface = addrindex_create_interface( ADDR_IF_JPILOT, "J-Pilot", TAG_IF_JPILOT, TAG_DS_JPILOT );
+       /* Create JPilot interface */
+       iface = addrindex_create_interface(
+                       ADDR_IF_JPILOT, "J-Pilot", TAG_IF_JPILOT,
+                       TAG_DS_JPILOT );
 #ifdef USE_JPILOT
        iface->haveLibrary = jpilot_test_pilot_lib();
        iface->useInterface = iface->haveLibrary;
@@ -196,72 +252,103 @@ static void addrindex_build_if_list( AddressIndex *addrIndex ) {
        iface->getAllPersons = ( void * ) jpilot_get_all_persons;
        iface->getName       = ( void * ) jpilot_get_name;
        iface->setAccessFlag = ( void * ) jpilot_set_accessed;
+       iface->searchOrder   = 3;
 #else
        iface->useInterface = FALSE;
        iface->haveLibrary = FALSE;
 #endif
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
-       iface = addrindex_create_interface( ADDR_IF_LDAP, "LDAP", TAG_IF_LDAP, TAG_DS_LDAP );
+       /* Create LDAP interface */
+       iface = addrindex_create_interface(
+                       ADDR_IF_LDAP, "LDAP", TAG_IF_LDAP, TAG_DS_LDAP );
 #ifdef USE_LDAP
-       iface->haveLibrary = syldap_test_ldap_lib();
+       /* iface->haveLibrary = ldapsvr_test_ldap_lib(); */
+       iface->haveLibrary = ldaputil_test_ldap_lib();
        iface->useInterface = iface->haveLibrary;
-       iface->getAccessFlag = ( void * ) syldap_get_accessed;
-       /* iface->getModifyFlag = ( void * ) syldap_get_modified; */
-       /* iface->getReadFlag   = ( void * ) syldap_get_read_flag; */
-       iface->getStatusCode = ( void * ) syldap_get_status;
-       iface->getReadData   = ( void * ) syldap_read_data;
-       iface->getRootFolder = ( void * ) syldap_get_root_folder;
-       iface->getListFolder = ( void * ) syldap_get_list_folder;
-       iface->getListPerson = ( void * ) syldap_get_list_person;
-       iface->getName       = ( void * ) syldap_get_name;
-       iface->setAccessFlag = ( void * ) syldap_set_accessed;
+       /* iface->getModifyFlag = ( void * ) ldapsvr_get_modified; */
+       iface->getAccessFlag = ( void * ) ldapsvr_get_accessed;
+       /* iface->getReadFlag   = ( void * ) ldapsvr_get_read_flag; */
+       iface->getStatusCode = ( void * ) ldapsvr_get_status;
+       /* iface->getReadData   = ( void * ) ldapsvr_read_data; */
+       iface->getRootFolder = ( void * ) ldapsvr_get_root_folder;
+       iface->getListFolder = ( void * ) ldapsvr_get_list_folder;
+       iface->getListPerson = ( void * ) ldapsvr_get_list_person;
+       iface->getName       = ( void * ) ldapsvr_get_name;
+       iface->setAccessFlag = ( void * ) ldapsvr_set_accessed;
+       iface->externalQuery = TRUE;
+       iface->searchOrder   = 1;
 #else
        iface->useInterface = FALSE;
        iface->haveLibrary = FALSE;
 #endif
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
-       /* Two old legacy data sources */
+       /* Two old legacy data sources (pre 0.7.0) */
        iface = addrindex_create_interface(
                        ADDR_IF_COMMON, "Old Address - common",
                        TAG_IF_OLD_COMMON, NULL );
        iface->legacyFlag = TRUE;
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
        iface = addrindex_create_interface(
                        ADDR_IF_COMMON, "Old Address - personal",
                        TAG_IF_OLD_PERSONAL, NULL );
        iface->legacyFlag = TRUE;
-       addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface );
+       addrIndex->interfaceList =
+               g_list_append( addrIndex->interfaceList, iface );
        ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex);
 
 }
 
-/*
-* Free name-value pairs.
-*/
-static void addrindex_free_attributes( GList *list ) {
-       GList *node = list;
+/**
+ * Free DOM fragment.
+ * \param fragment Fragment to free.
+ */
+static addrindex_free_fragment( AddressIfFragment *fragment ) {
+       GList *node;
+
+       /* Free children */
+       node = fragment->children;
+       while( node ) {
+               AddressIfFragment *child = node->data;
+               addrindex_free_fragment( child );
+               node->data = NULL;
+               node = g_list_next( node );
+       }
+       g_list_free( fragment->children );
+
+       /* Free attributes */
+       node = fragment->attributes;
        while( node ) {
                AddressIfAttrib *nv = node->data;
-               g_free( nv->name ); nv->name = NULL;
-               g_free( nv->value ); nv->value = NULL;
+               g_free( nv->name );
+               g_free( nv->value );
                g_free( nv );
                node->data = NULL;
                node = g_list_next( node );
        }
-       g_list_free( list );
+       g_list_free( fragment->attributes );
+
+       g_free( fragment->name );
+       fragment->name = NULL;
+       fragment->attributes = NULL;
+       fragment->children = NULL;
+
+       g_free( fragment );
 }
 
-/*
-* Create new data source.
-* Enter: ifType Interface type to create.
-* Return: Initialized data source.
-*/
+/**
+ * Create a new data source.
+ * \param ifType Interface type to create.
+ * \return Initialized data source.
+ */
 AddressDataSource *addrindex_create_datasource( AddressIfType ifType ) {
        AddressDataSource *ds = g_new0( AddressDataSource, 1 );
 
@@ -276,11 +363,13 @@ AddressDataSource *addrindex_create_datasource( AddressIfType ifType ) {
        return ds;
 }
 
-/*
-* Free up data source.
-*/
+/**
+ * Free up data source.
+ * \param ds Data source to free.
+ */
 void addrindex_free_datasource( AddressDataSource *ds ) {
        AddressInterface *iface;
+       AddressCache *cache;
 
        g_return_if_fail( ds != NULL );
 
@@ -304,16 +393,18 @@ void addrindex_free_datasource( AddressDataSource *ds ) {
 #endif
 #ifdef USE_LDAP
                                else if( iface->type == ADDR_IF_LDAP ) {
-                                       SyldapServer *server = ds->rawDataSource;
-                                       syldap_free( server );
+                                       LdapServer *server = ds->rawDataSource;
+                                       cache = server->addressCache;
+                                       addrcache_use_index( cache, FALSE );
+                                       ldapsvr_free( server );
                                }
 #endif
                                else {
                                }
                        }
                        else {
-                               GList *list = ds->rawDataSource;
-                               addrindex_free_attributes( list );
+                               AddressIfFragment *fragment = ds->rawDataSource;
+                               addrindex_free_fragment( fragment );
                        }
                }
        }
@@ -326,9 +417,14 @@ void addrindex_free_datasource( AddressDataSource *ds ) {
        ds->type = ADDR_IF_NONE;
        ds->interface = NULL;
        ds->rawDataSource = NULL;
+
        g_free( ds );
 }
 
+/**
+ * Free up all data sources for specified interface.
+ * \param iface Address interface to process.
+ */
 static void addrindex_free_all_datasources( AddressInterface *iface ) {
        GList *node = iface->listSource;
        while( node ) {
@@ -339,6 +435,10 @@ static void addrindex_free_all_datasources( AddressInterface *iface ) {
        }
 }
 
+/**
+ * Free up specified interface.
+ * \param iface Interface to process.
+ */
 static void addrindex_free_interface( AddressInterface *iface ) {
        /* Free up data sources */
        addrindex_free_all_datasources( iface );
@@ -366,14 +466,21 @@ static void addrindex_free_interface( AddressInterface *iface ) {
        iface->haveLibrary = FALSE;
        iface->listSource = NULL;
 
+       /* Search stuff */
+       iface->searchOrder = 0;
+       iface->startSearch = NULL;
+       iface->stopSearch = NULL;
+
        g_free( iface );
 }
 
-/*
+/**
  * Return cache ID for specified data source.
- * Enter: addrIndex Address index.
- *        ds        Data source.
- * Return: ID or NULL if not found. This can be g_free() when done.
+ *
+ * \param  addrIndex Address index.
+ * \param  ds        Data source.
+ * \return ID or NULL if not found. This should be <code>g_free()</code>
+ *         when done.
  */
 gchar *addrindex_get_cache_id( AddressIndex *addrIndex, AddressDataSource *ds ) {
        gchar *cacheID = NULL;
@@ -394,23 +501,25 @@ gchar *addrindex_get_cache_id( AddressIndex *addrIndex, AddressDataSource *ds )
        return cacheID;
 }
 
-/*
- * Return data source for specified cacheID.
- * Enter: addrIndex Address index.
- *        cacheID   ID.
- * Return: Data source, or NULL if not found.
+/**
+ * Return reference to data source for specified cacheID.
+ * \param addrIndex Address index.
+ * \param cacheID   ID.
+ * \return Data source, or NULL if not found.
  */
-AddressDataSource *addrindex_get_datasource( AddressIndex *addrIndex, const gchar *cacheID ) {
+AddressDataSource *addrindex_get_datasource(
+               AddressIndex *addrIndex, const gchar *cacheID )
+{
        g_return_val_if_fail( addrIndex != NULL, NULL );
        g_return_val_if_fail( cacheID != NULL, NULL );
        return ( AddressDataSource * ) g_hash_table_lookup( addrIndex->hashCache, cacheID );
 }
 
-/*
- * Return cache for specified cacheID.
- * Enter: addrIndex Address index.
- *        cacheID   ID.
- * Return: Address cache, or NULL if not found.
+/**
+ * Return reference to address cache for specified cacheID.
+ * \param addrIndex Address index.
+ * \param cacheID   ID.
+ * \return Address cache, or NULL if not found.
  */
 AddressCache *addrindex_get_cache( AddressIndex *addrIndex, const gchar *cacheID ) {
        AddressDataSource *ds;
@@ -429,12 +538,14 @@ AddressCache *addrindex_get_cache( AddressIndex *addrIndex, const gchar *cacheID
        return cache;
 }
 
-/*
- * Add data source into hash.
- * Enter: addrIndex Address index.
- *        ds        Data source.
+/**
+ * Add data source into hash table.
+ * \param addrIndex Address index.
+ * \param ds        Data source.
  */
-static void addrindex_hash_add_cache( AddressIndex *addrIndex, AddressDataSource *ds ) {
+static void addrindex_hash_add_cache(
+               AddressIndex *addrIndex, AddressDataSource *ds )
+{
        gchar *cacheID;
 
        cacheID = addrindex_get_cache_id( addrIndex, ds );
@@ -444,8 +555,8 @@ static void addrindex_hash_add_cache( AddressIndex *addrIndex, AddressDataSource
 }
 
 /*
-* Free hash table callback function.
-*/
+ * Free hash table callback function.
+ */
 static gboolean addrindex_free_cache_cb( gpointer key, gpointer value, gpointer data ) {
        g_free( key );
        key = NULL;
@@ -454,8 +565,8 @@ static gboolean addrindex_free_cache_cb( gpointer key, gpointer value, gpointer
 }
 
 /*
-* Free hash table of address cache items.
-*/
+ * Free hash table of address cache items.
+ */
 static void addrindex_free_cache_hash( GHashTable *table ) {
        g_hash_table_freeze( table );
        g_hash_table_foreach_remove( table, addrindex_free_cache_cb, NULL );
@@ -464,9 +575,13 @@ static void addrindex_free_cache_hash( GHashTable *table ) {
 }
 
 /*
-* Remove address cache for specified data source from internal hashtable.
-*/
-static void addrindex_hash_remove_cache( AddressIndex *addrIndex, AddressDataSource *ds ) {
+ * Remove data source from internal hashtable.
+ * \param addrIndex Address index.
+ * \param ds        Data source to remove.
+ */
+static void addrindex_hash_remove_cache(
+               AddressIndex *addrIndex, AddressDataSource *ds )
+{
        gchar *cacheID;
 
        cacheID = addrindex_get_cache_id( addrIndex, ds );
@@ -478,8 +593,9 @@ static void addrindex_hash_remove_cache( AddressIndex *addrIndex, AddressDataSou
 }
 
 /*
-* Create new object.
-*/
+ * Create a new address index.
+ * \return Initialized address index object.
+ */
 AddressIndex *addrindex_create_index( void ) {
        AddressIndex *addrIndex = g_new0( AddressIndex, 1 );
 
@@ -498,46 +614,83 @@ AddressIndex *addrindex_create_index( void ) {
        addrIndex->lastType = ADDR_IF_NONE;
        addrIndex->dirtyFlag = FALSE;
        addrIndex->hashCache = g_hash_table_new( g_str_hash, g_str_equal );
+       addrIndex->loadedFlag = FALSE;
+       addrIndex->searchOrder = NULL;
        addrindex_build_if_list( addrIndex );
        return addrIndex;
 }
 
-/*
-* Specify file to be used.
-*/
+/**
+ * Property - Specify file path to address index file.
+ * \param addrIndex Address index.
+ * \param value Path to index file.
+ */
 void addrindex_set_file_path( AddressIndex *addrIndex, const gchar *value ) {
        g_return_if_fail( addrIndex != NULL );
        addrIndex->filePath = mgu_replace_string( addrIndex->filePath, value );
 }
+
+/**
+ * Property - Specify file name to address index file.
+ * \param addrIndex Address index.
+ * \param value File name.
+ */
 void addrindex_set_file_name( AddressIndex *addrIndex, const gchar *value ) {
        g_return_if_fail( addrIndex != NULL );
        addrIndex->fileName = mgu_replace_string( addrIndex->fileName, value );
 }
+
+/**
+ * Property - Specify file path to be used.
+ * \param addrIndex Address index.
+ * \param value Path to JPilot file.
+ */
 void addrindex_set_dirty( AddressIndex *addrIndex, const gboolean value ) {
        g_return_if_fail( addrIndex != NULL );
        addrIndex->dirtyFlag = value;
 }
 
-/*
-* Return list of interfaces.
-*/
+/**
+ * Property - get loaded flag. Note that this flag is set after reading data
+ * from the address books.
+ * \param addrIndex Address index.
+ * \return <i>TRUE</i> if address index data was loaded.
+ */
+gboolean addrindex_get_loaded( AddressIndex *addrIndex ) {
+       g_return_val_if_fail( addrIndex != NULL, FALSE );
+       return addrIndex->loadedFlag;
+}
+
+/**
+ * Return list of address interfaces.
+ * \param addrIndex Address index.
+ * \return List of address interfaces.
+ */
 GList *addrindex_get_interface_list( AddressIndex *addrIndex ) {
        g_return_val_if_fail( addrIndex != NULL, NULL );
        return addrIndex->interfaceList;
 }
 
-/*
-* Free up object.
-*/
+/**
+ * Free up address index.
+ * \param addrIndex Address index.
+ */
 void addrindex_free_index( AddressIndex *addrIndex ) {
        GList *node;
 
        g_return_if_fail( addrIndex != NULL );
 
+       /* Search stuff */
+       g_list_free( addrIndex->searchOrder );
+       addrIndex->searchOrder = NULL;
+
+       /* Free internal storage */
        g_free( ADDRITEM_ID(addrIndex) );
        g_free( ADDRITEM_NAME(addrIndex) );
        g_free( addrIndex->filePath );
        g_free( addrIndex->fileName );
+
+       /* Clear pointers */    
        ADDRITEM_TYPE(addrIndex) = ITEMTYPE_NONE;
        ADDRITEM_ID(addrIndex) = NULL;
        ADDRITEM_NAME(addrIndex) = NULL;
@@ -551,6 +704,8 @@ void addrindex_free_index( AddressIndex *addrIndex ) {
        addrIndex->conversionError = FALSE;
        addrIndex->lastType = ADDR_IF_NONE;
        addrIndex->dirtyFlag = FALSE;
+
+       /* Free up interfaces */        
        node = addrIndex->interfaceList;
        while( node ) {
                AddressInterface *iface = node->data;
@@ -559,28 +714,40 @@ void addrindex_free_index( AddressIndex *addrIndex ) {
        }
        g_list_free( addrIndex->interfaceList );
        addrIndex->interfaceList = NULL;
+
+       /* Free up hash cache */
        addrindex_free_cache_hash( addrIndex->hashCache );
        addrIndex->hashCache = NULL;
+
+       addrIndex->loadedFlag = FALSE;
+
        g_free( addrIndex );
 }
 
-/*
-* Print address index.
+/**
+ * Print address index.
+ * \param addrIndex Address index.
+ * \parem stream    Stream to print.
 */
 void addrindex_print_index( AddressIndex *addrIndex, FILE *stream ) {
        g_return_if_fail( addrIndex != NULL );
        fprintf( stream, "AddressIndex:\n" );
        fprintf( stream, "\tfile path: '%s'\n", addrIndex->filePath );
        fprintf( stream, "\tfile name: '%s'\n", addrIndex->fileName );
-       fprintf( stream, "\t   status: %d\n",   addrIndex->retVal );
-       fprintf( stream, "\tconverted: '%s'\n", addrIndex->wasConverted ? "yes" : "no" );
-       fprintf( stream, "\tcvt error: '%s'\n", addrIndex->conversionError ? "yes" : "no" );
+       fprintf( stream, "\t   status: %d\n", addrIndex->retVal );
+       fprintf( stream, "\tconverted: '%s'\n",
+                       addrIndex->wasConverted ? "yes" : "no" );
+       fprintf( stream, "\tcvt error: '%s'\n",
+                       addrIndex->conversionError ? "yes" : "no" );
        fprintf( stream, "\t---\n" );
 }
 
-/*
-* Retrieve specified interface from index.
-*/
+/**
+ * Retrieve reference to address interface for specified interface type.
+ * \param  addrIndex Address index.
+ * \param  ifType Interface type.
+ * \return Address interface, or NULL if not found.
+ */
 AddressInterface *addrindex_get_interface(
        AddressIndex *addrIndex, AddressIfType ifType )
 {
@@ -601,15 +768,16 @@ AddressInterface *addrindex_get_interface(
        return retVal;
 }
 
-/*
-* Add data source to index.
-* Enter: addrIndex  Address index object.
-*        ifType     Interface type to add.
-*        dataSource Actual data source to add.
-* Return: TRUE if data source was added.
-* Note: The raw data object (for example, AddressBookFile or VCardFile object) should be
-* supplied as the dataSource argument.
-*/
+/**
+ * Add raw data source to index. The raw data object (an AddressBookFile or
+ * VCardFile object, for example) should be supplied as the raw dataSource
+ * argument.
+ *
+ * \param  addrIndex Address index.
+ * \param ifType     Interface type to add.
+ * \param dataSource Actual raw data source to add. 
+ * \return Data source added, or NULL if invalid interface type.
+ */
 AddressDataSource *addrindex_index_add_datasource(
        AddressIndex *addrIndex, AddressIfType ifType, gpointer dataSource )
 {
@@ -634,13 +802,13 @@ AddressDataSource *addrindex_index_add_datasource(
        return ds;
 }
 
-/*
-* Remove data source from index.
-* Enter: addrIndex  Address index object.
-*        dataSource Data source to remove.
-* Return: Data source if removed, or NULL if data source was not found in
-* index. Note the this object must still be freed.
-*/
+/**
+ * Remove specified data source from index.
+ * \param  addrIndex Address index.
+ * \param  dataSource Data source to add. 
+ * \return Reference to data source if removed, or NULL if data source was not
+ *         found in index. Note the this object must still be freed.
+ */
 AddressDataSource *addrindex_index_remove_datasource(
        AddressIndex *addrIndex, AddressDataSource *dataSource )
 {
@@ -664,6 +832,14 @@ AddressDataSource *addrindex_index_remove_datasource(
        return retVal;
 }
 
+/**
+ * Retrieve a reference to address interface for specified interface type and
+ * XML interface tag name.
+ * \param  addrIndex Address index.
+ * \param  tag       XML interface tag name to match.
+ * \param  ifType    Interface type to match.
+ * \return Reference to address index, or NULL if not found in index.
+ */
 static AddressInterface *addrindex_tag_get_interface(
        AddressIndex *addrIndex, gchar *tag, AddressIfType ifType )
 {
@@ -689,6 +865,14 @@ static AddressInterface *addrindex_tag_get_interface(
        return retVal;
 }
 
+/**
+ * Retrieve a reference to address interface for specified interface type and
+ * XML datasource tag name.
+ * \param  addrIndex Address index.
+ * \param  ifType    Interface type to match.
+ * \param  tag       XML datasource tag name to match.
+ * \return Reference to address index, or NULL if not found in index.
+ */
 static AddressInterface *addrindex_tag_get_datasource(
        AddressIndex *addrIndex, AddressIfType ifType, gchar *tag )
 {
@@ -712,26 +896,27 @@ static AddressInterface *addrindex_tag_get_datasource(
 * Interface XML parsing functions.
 * ***********************************************************************
 */
-/*
-static void show_attribs( GList *attr ) {
-       while( attr ) {
-               gchar *name = ((XMLAttr *)attr->data)->name;
-               gchar *value = ((XMLAttr *)attr->data)->value;
-               printf( "\tattr value : %s :%s:\n", name, value );
-               attr = g_list_next( attr );
-       }
-       printf( "\t---\n" );
-}
-*/
 
-static void addrindex_write_elem_s( FILE *fp, gint lvl, gchar *name ) {
+/**
+ * Write start of XML element to file.
+ * \param fp   File.
+ * \param lvl  Indentation level.
+ * \param name Element name.
+ */
+static void addrindex_write_elem_s( FILE *fp, const gint lvl, const gchar *name ) {
        gint i;
        for( i = 0; i < lvl; i++ ) fputs( "  ", fp );
        fputs( "<", fp );
        fputs( name, fp );
 }
 
-static void addrindex_write_elem_e( FILE *fp, gint lvl, gchar *name ) {
+/**
+ * Write end of XML element to file.
+ * \param fp   File.
+ * \param lvl  Indentation level.
+ * \param name Element name.
+ */
+static void addrindex_write_elem_e( FILE *fp, const gint lvl, const gchar *name ) {
        gint i;
        for( i = 0; i < lvl; i++ ) fputs( "  ", fp );
        fputs( "</", fp );
@@ -739,7 +924,13 @@ static void addrindex_write_elem_e( FILE *fp, gint lvl, gchar *name ) {
        fputs( ">\n", fp );
 }
 
-static void addrindex_write_attr( FILE *fp, gchar *name, gchar *value ) {
+/**
+ * Write XML attribute to file.
+ * \param fp    File.
+ * \param name  Attribute name.
+ * \param value Attribute value.
+ */
+static void addrindex_write_attr( FILE *fp, const gchar *name, const gchar *value ) {
        fputs( " ", fp );
        fputs( name, fp );
        fputs( "=\"", fp );
@@ -747,16 +938,36 @@ static void addrindex_write_attr( FILE *fp, gchar *name, gchar *value ) {
        fputs( "\"", fp );
 }
 
-/*
-* Return list of name-value pairs.
-*/
-static GList *addrindex_read_attributes( XMLFile *file ) {
-       GList *list = NULL;
+/**
+ * Return DOM fragment for current XML tag from file.
+ * \param  file XML file being processed.
+ * \return Fragment representing DOM fragment for configuration element.
+ */
+static AddressIfFragment *addrindex_read_fragment( XMLFile *file ) {
+       AddressIfFragment *fragment;
+       AddressIfFragment *child;
        AddressIfAttrib *nv;
+       XMLTag *xtag;
+       GList *list;
        GList *attr;
        gchar *name;
        gchar *value;
+       guint prevLevel;
+       gint rc;
+
+       prevLevel = file->level;
+
+       /* Get current tag name */
+       xtag = xml_get_current_tag( file );
+
+       /* Create new fragment */
+       fragment = g_new0( AddressIfFragment, 1 );
+       fragment->name = g_strdup( xtag->tag );
+       fragment->children = NULL;
+       fragment->attributes = NULL;
 
+       /* Read attributes */
+       list = NULL;
        attr = xml_get_current_tag_attr( file );
        while( attr ) {
                name = ((XMLAttr *)attr->data)->name;
@@ -767,38 +978,102 @@ static GList *addrindex_read_attributes( XMLFile *file ) {
                list = g_list_append( list, nv );
                attr = g_list_next( attr );
        }
-       return list;
+       fragment->attributes = list;
+
+       /* Now read the children */
+       while( TRUE ) {
+               rc = xml_parse_next_tag( file );
+               if( rc != 0 ) {
+                       /* End of file? */
+                       break;
+               }
+               if( file->level < prevLevel ) {
+                       /* We must be above level we start at */
+                       break;
+               }
+               child = addrindex_read_fragment( file );
+               fragment->children = g_list_append( fragment->children, child );
+       }
+
+       return fragment;
 }
 
-/*
-* Output name-value pairs.
-*/
-static void addrindex_write_attributes( FILE *fp, gchar *tag, GList *list, gint lvl ) {
+/**
+ * Write DOM fragment to file.
+ * \param fp       File to write.
+ * \param fragment DOM fragment for configuration element.
+ * \param lvl      Indent level.
+ */
+static void addrindex_write_fragment(
+               FILE *fp, const AddressIfFragment *fragment, const gint lvl )
+{
        GList *node;
-       AddressIfAttrib *nv;
-       if( list ) {
-               addrindex_write_elem_s( fp, lvl, tag );
-               node = list;
+
+       if( fragment ) {
+               addrindex_write_elem_s( fp, lvl, fragment->name );
+               node = fragment->attributes;
                while( node ) {
-                       nv = node->data;
+                       AddressIfAttrib *nv = node->data;
                        addrindex_write_attr( fp, nv->name, nv->value );
                        node = g_list_next( node );
                }
-               fputs(" />\n", fp);
+               if( fragment->children ) {
+                       fputs(" >\n", fp);
+
+                       /* Output children */
+                       node = fragment->children;
+                       while( node ) {
+                               AddressIfFragment *child = node->data;
+                               addrindex_write_fragment( fp, child, 1+lvl );
+                               node = g_list_next( node );
+                       }
+
+                       /* Output closing tag */
+                       addrindex_write_elem_e( fp, lvl, fragment->name );
+               }
+               else {
+                       fputs(" />\n", fp);
+               }
        }
 }
 
 /*
-static void addrindex_print_attributes( GList *list, FILE *stream ) {
-       GList *node = list;
+static void addrindex_print_fragment_r(
+               const AddressIfFragment *fragment, FILE *stream, gint lvl )
+{
+       GList *node;
+       gint i;
+
+       for( i = 0; i < lvl; i++ )
+               fprintf( stream, "  " );
+       fprintf( stream, "Element:%s:\n", fragment->name );
+       node = fragment->attributes;
        while( node ) {
                AddressIfAttrib *nv = node->data;
-               fprintf( stream, "%s : %s\n", nv->name, nv->value );
+               for( i = 0; i < lvl; i++ )
+                       fprintf( stream, "  " );
+               fprintf( stream, "    %s : %s\n", nv->name, nv->value );
+               node = g_list_next( node );
+       }
+       node = fragment->children;
+       while( node ) {
+               AddressIfFragment *child = node->data;
+               addrindex_print_fragment_r( child, stream, 1+lvl );
                node = g_list_next( node );
        }
 }
+
+static void addrindex_print_fragment( const AddressIfFragment *fragment, FILE *stream ) {
+       addrindex_print_fragment_r( fragment, stream, 0 );
+}
 */
 
+/**
+ * Read/parse address index file, creating a data source for a regular
+ * intrinsic XML addressbook.
+ * \param  file Address index file.
+ * \return Data source.
+ */
 static AddressDataSource *addrindex_parse_book( XMLFile *file ) {
        AddressDataSource *ds;
        AddressBookFile *abf;
@@ -923,110 +1198,258 @@ static void addrindex_write_jpilot( FILE *fp,AddressDataSource *ds, gint lvl ) {
                fputs( " />\n", fp );
        }
 }
+
 #else
-/* Just read/write name-value pairs (preserve data found in file)  */
+/*
+ * Just read/write DOM fragments (preserve data found in file).
+ */
 static AddressDataSource *addrindex_parse_jpilot( XMLFile *file ) {
        AddressDataSource *ds;
 
        ds = addrindex_create_datasource( ADDR_IF_JPILOT );
-       ds->rawDataSource = addrindex_read_attributes( file );
+       ds->rawDataSource = addrindex_read_fragment( file );
        return ds;
 }
 
 static void addrindex_write_jpilot( FILE *fp, AddressDataSource *ds, gint lvl ) {
-       GList *list = ds->rawDataSource;
-       if( list ) {
-               addrindex_write_attributes( fp, TAG_DS_JPILOT, list, lvl );
+       AddressIfFragment *fragment = ds->rawDataSource;
+       if( fragment ) {
+               addrindex_write_fragment( fp, fragment, lvl );
        }
 }
 #endif
 
 #ifdef USE_LDAP
+/**
+ * Parse LDAP criteria attribute data from XML file.
+ * \param file Index file.
+ * \param ctl  LDAP control object to populate.
+ */
+static void addrindex_parse_ldap_attrlist( XMLFile *file, LdapControl *ctl ) {
+       guint prevLevel;
+       XMLTag *xtag;
+       XMLTag *xtagPrev;
+       gint rc;
+       GList *attr;
+       GList *list;
+       GList *node;
+
+       if( file == NULL ) {
+               return;
+       }
+
+       list = NULL;
+       prevLevel = file->level;
+       xtagPrev = xml_get_current_tag( file );
+       while( TRUE ) {
+               rc = xml_parse_next_tag( file );
+               if( rc != 0 ) {
+                       /* Terminate prematurely */
+                       mgu_free_dlist( list );
+                       list = NULL;
+                       return;
+               }
+               if( file->level < prevLevel ) {
+                       /* We must be above level we start at */
+                       break;
+               }
+
+               /* Get a tag (element) */
+               xtag = xml_get_current_tag( file );
+               if( strcmp( xtag->tag, ELTAG_LDAP_ATTR_SRCH ) == 0 ) {
+                       /* LDAP criteria attribute */
+                       attr = xml_get_current_tag_attr( file );
+                       while( attr ) {
+                               gchar *name = ((XMLAttr *)attr->data)->name;
+                               gchar *value = ((XMLAttr *)attr->data)->value;
+                               if( strcmp( name, ATTAG_LDAP_ATTR_NAME ) == 0 ) {
+                                       if( value && strlen( value ) > 0 ) {
+                                               list = g_list_append(
+                                                       list, g_strdup( value ) );
+                                       }
+                               }
+                               attr = g_list_next( attr );
+                       }
+               }
+               else {
+                       if( xtag != xtagPrev ) {
+                               /* Found a new tag */
+                               break;
+                       }
+               }
+               xtag = xtagPrev;
+       }
+
+       /* Build list of search attributes */
+       ldapctl_criteria_list_clear( ctl );
+       node = list;
+       while( node ) {
+               ldapctl_criteria_list_add( ctl, node->data );
+               g_free( node->data );
+               node->data = NULL;
+               node = g_list_next( node );
+       }
+       g_list_free( list );
+       list = NULL;
+
+}
+
 static AddressDataSource *addrindex_parse_ldap( XMLFile *file ) {
        AddressDataSource *ds;
-       SyldapServer *server;
+       LdapServer *server;
+       LdapControl *ctl;
        GList *attr;
+       gchar *serverName = NULL;
+       gchar *criteria = NULL;
+       gboolean bSearch = FALSE;
+       gboolean cvtFlag = TRUE;
 
        ds = addrindex_create_datasource( ADDR_IF_LDAP );
-       server = syldap_create();
+       ctl = ldapctl_create();
        attr = xml_get_current_tag_attr( file );
        while( attr ) {
                gchar *name = ((XMLAttr *)attr->data)->name;
                gchar *value = ((XMLAttr *)attr->data)->value;
                gint ivalue = atoi( value );
+
                if( strcmp( name, ATTAG_LDAP_NAME ) == 0 ) {
-                       syldap_set_name( server, value );
+                       if( serverName ) g_free( serverName );
+                       serverName = g_strdup( value );
                }
                else if( strcmp( name, ATTAG_LDAP_HOST ) == 0 ) {
-                       syldap_set_host( server, value );
+                       ldapctl_set_host( ctl, value );
                }
                else if( strcmp( name, ATTAG_LDAP_PORT ) == 0 ) {
-                       syldap_set_port( server, ivalue );
+                       ldapctl_set_port( ctl, ivalue );
                }
                else if( strcmp( name, ATTAG_LDAP_BASE_DN ) == 0 ) {
-                       syldap_set_base_dn( server, value );
+                       ldapctl_set_base_dn( ctl, value );
                }
                else if( strcmp( name, ATTAG_LDAP_BIND_DN ) == 0 ) {
-                       syldap_set_bind_dn( server, value );
+                       ldapctl_set_bind_dn( ctl, value );
                }
                else if( strcmp( name, ATTAG_LDAP_BIND_PASS ) == 0 ) {
-                       syldap_set_bind_password( server, value );
+                       ldapctl_set_bind_password( ctl, value );
                }
                else if( strcmp( name, ATTAG_LDAP_CRITERIA ) == 0 ) {
-                       syldap_set_search_criteria( server, value );
+                       if( criteria ) g_free( criteria );
+                       criteria = g_strdup( value );
                }
                else if( strcmp( name, ATTAG_LDAP_MAX_ENTRY ) == 0 ) {
-                       syldap_set_max_entries( server, ivalue );
+                       ldapctl_set_max_entries( ctl, ivalue );
                }
                else if( strcmp( name, ATTAG_LDAP_TIMEOUT ) == 0 ) {
-                       syldap_set_timeout( server, ivalue );
+                       ldapctl_set_timeout( ctl, ivalue );
+               }
+               else if( strcmp( name, ATTAG_LDAP_MAX_AGE ) == 0 ) {
+                       ldapctl_set_max_query_age( ctl, ivalue );
+               }
+               else if( strcmp( name, ATTAG_LDAP_DYN_SEARCH ) == 0 ) {
+                       bSearch = FALSE;
+                       cvtFlag = FALSE;
+                       if( strcmp( value, "yes" ) == 0 ) {
+                               bSearch = TRUE;
+                       }
                }
                attr = g_list_next( attr );
        }
 
+       server = ldapsvr_create();
+       ldapsvr_set_name( server, serverName );
+       ldapsvr_set_search_flag( server, bSearch );
+       g_free( serverName );
+       ldapsvr_set_control( server, ctl );
        ds->rawDataSource = server;
+
+       addrindex_parse_ldap_attrlist( file, ctl );
+       /*
+        * If criteria have been specified and no attributes were listed, then
+        * convert old style criteria into an attribute list. Any criteria will
+        * be dropped when saving data.
+        */
+       if( criteria ) {
+               if( ! ldapctl_get_criteria_list( ctl ) ) {
+                       ldapctl_parse_ldap_search( ctl, criteria );
+               }
+               g_free( criteria );
+       }
+       /*
+        * If no search flag was found, then we are converting from old format
+        * server data to new format.
+        */
+       if( cvtFlag ) {
+               ldapsvr_set_search_flag( server, TRUE );
+       }
+       /* ldapsvr_print_data( server, stdout ); */
+
        return ds;
 }
 
 static void addrindex_write_ldap( FILE *fp, AddressDataSource *ds, gint lvl ) {
-       SyldapServer *server = ds->rawDataSource;
+       LdapServer *server = ds->rawDataSource;
+       LdapControl *ctl = NULL;
+       GList *node;
+       gchar value[256];
+
        if( server ) {
-               gchar value[256];
+               ctl = server->control;
+       }
+       if( ctl == NULL ) return;
+
+       /* Output start element with attributes */
+       addrindex_write_elem_s( fp, lvl, TAG_DS_LDAP );
+       addrindex_write_attr( fp, ATTAG_LDAP_NAME, ldapsvr_get_name( server ) );
+       addrindex_write_attr( fp, ATTAG_LDAP_HOST, ctl->hostName );
+
+       sprintf( value, "%d", ctl->port );      
+       addrindex_write_attr( fp, ATTAG_LDAP_PORT, value );
 
-               addrindex_write_elem_s( fp, lvl, TAG_DS_LDAP );
-               addrindex_write_attr( fp, ATTAG_LDAP_NAME, syldap_get_name( server ) );
-               addrindex_write_attr( fp, ATTAG_LDAP_HOST, server->hostName );
+       addrindex_write_attr( fp, ATTAG_LDAP_BASE_DN, ctl->baseDN );
+       addrindex_write_attr( fp, ATTAG_LDAP_BIND_DN, ctl->bindDN );
+       addrindex_write_attr( fp, ATTAG_LDAP_BIND_PASS, ctl->bindPass );
 
-               sprintf( value, "%d", server->port );   
-               addrindex_write_attr( fp, ATTAG_LDAP_PORT, value );
+       sprintf( value, "%d", ctl->maxEntries );
+       addrindex_write_attr( fp, ATTAG_LDAP_MAX_ENTRY, value );
+       sprintf( value, "%d", ctl->timeOut );
+       addrindex_write_attr( fp, ATTAG_LDAP_TIMEOUT, value );
+       sprintf( value, "%d", ctl->maxQueryAge );
+       addrindex_write_attr( fp, ATTAG_LDAP_MAX_AGE, value );
 
-               addrindex_write_attr( fp, ATTAG_LDAP_BASE_DN, server->baseDN );
-               addrindex_write_attr( fp, ATTAG_LDAP_BIND_DN, server->bindDN );
-               addrindex_write_attr( fp, ATTAG_LDAP_BIND_PASS, server->bindPass );
-               addrindex_write_attr( fp, ATTAG_LDAP_CRITERIA, server->searchCriteria );
+       addrindex_write_attr( fp, ATTAG_LDAP_DYN_SEARCH,
+                       server->searchFlag ? "yes" : "no" );
 
-               sprintf( value, "%d", server->maxEntries );
-               addrindex_write_attr( fp, ATTAG_LDAP_MAX_ENTRY, value );
-               sprintf( value, "%d", server->timeOut );
-               addrindex_write_attr( fp, ATTAG_LDAP_TIMEOUT, value );
+       fputs(" >\n", fp);
 
+       /* Output attributes */
+       node = ldapctl_get_criteria_list( ctl );
+       while( node ) {
+               addrindex_write_elem_s( fp, 1+lvl, ELTAG_LDAP_ATTR_SRCH );
+               addrindex_write_attr( fp, ATTAG_LDAP_ATTR_NAME, node->data );
                fputs(" />\n", fp);
+               node = g_list_next( node );
        }
+
+       /* End of element */    
+       addrindex_write_elem_e( fp, lvl, TAG_DS_LDAP );
+
 }
+
 #else
-/* Just read/write name-value pairs (preserve data found in file)  */
+/*
+ * Just read/write DOM fragments (preserve data found in file).
+ */
 static AddressDataSource *addrindex_parse_ldap( XMLFile *file ) {
        AddressDataSource *ds;
 
        ds = addrindex_create_datasource( ADDR_IF_LDAP );
-       ds->rawDataSource = addrindex_read_attributes( file );
+       ds->rawDataSource = addrindex_read_fragment( file );
        return ds;
 }
 
 static void addrindex_write_ldap( FILE *fp, AddressDataSource *ds, gint lvl ) {
-       GList *list = ds->rawDataSource;
-       if( list ) {
-               addrindex_write_attributes( fp, TAG_DS_LDAP, list, lvl );
+       AddressIfFragment *fragment = ds->rawDataSource;
+       if( fragment ) {
+               addrindex_write_fragment( fp, fragment, lvl );
        }
 }
 #endif
@@ -1035,16 +1458,25 @@ static void addrindex_write_ldap( FILE *fp, AddressDataSource *ds, gint lvl ) {
 * Address index I/O functions.
 * ***********************************************************************
 */
+/**
+ * Read address index file, creating appropriate data sources for each address
+ * index file entry.
+ *
+ * \param  addrIndex Address index.
+ * \param  file Address index file.
+ */
 static void addrindex_read_index( AddressIndex *addrIndex, XMLFile *file ) {
        guint prev_level;
        XMLTag *xtag;
        AddressInterface *iface = NULL, *dsIFace = NULL;
        AddressDataSource *ds;
+       gint rc;
 
+       addrIndex->loadedFlag = FALSE;
        for (;;) {
                prev_level = file->level;
-               xml_parse_next_tag( file );
-               if( file->level < prev_level ) return;
+               rc = xml_parse_next_tag( file );
+               if( file->level == 0 ) return;
 
                xtag = xml_get_current_tag( file );
 
@@ -1083,10 +1515,52 @@ static void addrindex_read_index( AddressIndex *addrIndex, XMLFile *file ) {
                                }
                        }
                }
-               addrindex_read_index( addrIndex, file );
        }
 }
 
+/*
+ * Search order sorting comparison function for building search order list.
+ */
+static gint addrindex_search_order_compare( gconstpointer ptrA, gconstpointer ptrB ) {
+       AddressInterface *ifaceA = ( AddressInterface * ) ptrA;
+       AddressInterface *ifaceB = ( AddressInterface * ) ptrB;
+
+       return ifaceA->searchOrder - ifaceB->searchOrder;
+}
+
+/**
+ * Build list of data sources to process.
+ * \param addrIndex Address index object.
+ */
+static void addrindex_build_search_order( AddressIndex *addrIndex ) {
+       AddressInterface *iface;
+       GList *nodeIf;
+
+       /* Clear existing list */
+       g_list_free( addrIndex->searchOrder );
+       addrIndex->searchOrder = NULL;
+
+       /* Build new list */
+       nodeIf = addrIndex->interfaceList;
+       while( nodeIf ) {
+               AddressInterface *iface = nodeIf->data;
+               if( iface->searchOrder > 0 ) {
+                       /* Add to search order list */
+                       addrIndex->searchOrder = g_list_insert_sorted(
+                               addrIndex->searchOrder, iface,
+                               addrindex_search_order_compare );
+               }
+               nodeIf = g_list_next( nodeIf );
+       }
+
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               AddressInterface *iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+       }
+
+}
+
 static gint addrindex_read_file( AddressIndex *addrIndex ) {
        XMLFile *file = NULL;
        gchar *fileSpec = NULL;
@@ -1099,7 +1573,9 @@ static gint addrindex_read_file( AddressIndex *addrIndex ) {
        g_free( fileSpec );
 
        if( file == NULL ) {
-               /* fprintf( stdout, " file '%s' does not exist.\n", addrIndex->fileName ); */
+               /*
+               fprintf( stdout, " file '%s' does not exist.\n", addrIndex->fileName );
+               */
                return addrIndex->retVal;
        }
 
@@ -1114,6 +1590,8 @@ static gint addrindex_read_file( AddressIndex *addrIndex ) {
        }
        xml_close_file( file );
 
+       addrindex_build_search_order( addrIndex );
+
        return addrIndex->retVal;
 }
 
@@ -1954,6 +2432,522 @@ GList *addrindex_ds_get_all_groups( AddressDataSource *ds ) {
        return retVal;
 }
 
-/*
-* End of Source.
+/* **********************************************************************
+* Address search stuff.
+* ***********************************************************************
 */
+
+/**
+ * Current query ID. This is incremented for each query created.
+ */
+static gint _currentQueryID_ = 0;
+
+/*
+ * Variables for the search that is being performed.
+ */
+static gchar *_searchTerm_ = NULL;
+static gpointer _searchTarget_ = NULL;
+static AddrSearchCallbackFunc *_searchCallback_ = NULL;
+
+/**
+ * Setup or register the search that will be performed.
+ * \param addrIndex  Address index object.
+ * \param searchTerm Search term. A private copy will be made.
+ * \param target     Target object that will receive data.
+ * \param callBack   Callback function.
+ * \return ID allocated to query that will be executed.
+ */
+gint addrindex_setup_search(
+       AddressIndex *addrIndex, const gchar *searchTerm,
+       const gpointer target, AddrSearchCallbackFunc callBack )
+{
+       gint queryID;
+
+       /* printf( "search term ::%s::\n", searchTerm ); */
+       g_free( _searchTerm_ );
+       _searchTerm_ = g_strdup( searchTerm );
+
+       queryID = ++_currentQueryID_;
+       _searchTarget_ = target;
+       _searchCallback_ = callBack;
+       /* printf( "query ID ::%d::\n", queryID ); */
+       return queryID;
+}
+
+/**
+ * Perform the search for specified address cache.
+ * \param cache Cache to be searched.
+ * \param queryID ID of search query to be executed.
+ */
+static void addrindex_search_cache( AddressCache *cache, const gint queryID ) {
+       AddrCacheIndex *index;
+       GList *listEMail;
+
+       index = cache->searchIndex;
+       if( index == NULL ) return;
+       if( index->invalid ) {
+               addrcache_build_index( cache );
+       }
+
+       /*
+       printf( "query ::%d:: searching index for ::%s::\n", queryID, _searchTerm_ );
+       */
+       listEMail = addrcindex_search( index, _searchTerm_ );
+       ( _searchCallback_ ) ( queryID, listEMail, _searchTarget_ );
+       g_list_free( listEMail );
+       listEMail = NULL;
+       /* printf( "searching index done\n" ); */
+}
+
+#ifdef USE_LDAP
+/**
+ * LDAP callback entry point for each address entry found.
+ * \param qry       LDAP query.
+ * \param listEMail List of Item EMail objects found.
+ */
+static void addrindex_ldap_entry_cb( LdapQuery *qry, GList *listEMail ) {
+       GList *node;
+
+       /*
+       printf( "\naddrindex::addrindex_ldap_entry_cb ::%s::\n", qry->queryName );
+       */
+       node = listEMail;
+       while( node ) {
+               ItemEMail *email = node->data;
+               /* printf( "\temail ::%s::\n", email->address ); */
+               node = g_list_next( node );
+       }
+       if( _searchCallback_ ) {
+               ( _searchCallback_ ) ( qry->queryID, listEMail, _searchTarget_ );
+       }
+       g_list_free( listEMail );
+}
+
+/**
+ * LDAP callback entry point for completion of search.
+ * \param qry LDAP query.
+ */
+static void addrindex_ldap_end_cb( LdapQuery *qry ) {
+       /* printf( "\naddrindex::addrindex_ldap_end_cb ::%s::\n", qry->queryName ); */
+}
+
+/**
+ * Return results of previous query.
+ * \param folder.
+ * \return List of ItemEMail objects.
+ */
+static void addrindex_ldap_use_previous( const ItemFolder *folder, const gint queryID )
+{
+       GList *listEMail;
+       GList *node;
+       GList *nodeEM;
+
+       listEMail = NULL;
+       if( _searchCallback_ ) {
+               node = folder->listPerson;
+               while( node ) {
+                       AddrItemObject *aio = node->data;
+                       if( aio &&  aio->type == ITEMTYPE_PERSON ) {
+                               ItemPerson *person = node->data;
+                               nodeEM = person->listEMail;
+                               while( nodeEM ) {
+                                       ItemEMail *email = nodeEM->data;
+                                       nodeEM = g_list_next( nodeEM );
+                                       listEMail = g_list_append( listEMail, email );
+                               }
+                       }
+                       node = g_list_next( node );
+               }
+               ( _searchCallback_ ) ( queryID, listEMail, _searchTarget_ );
+               g_list_free( listEMail );
+       }
+}
+
+LdapQuery *ldapsvr_locate_query( LdapServer *server, const gchar *searchTerm );
+
+/**
+ * Construct an LDAP query and initiate an LDAP search.
+ * \param server  LDAP server object.
+ * \param queryID ID of search query to be executed.
+ */
+static void addrindex_search_ldap( LdapServer *server, const gint queryID ) {
+       LdapQuery *qry;
+       gchar *name;
+
+       if( ! server->searchFlag ) return;
+       printf( "Searching ::%s::\n", ldapsvr_get_name( server ) );
+
+       /* Retire any aged queries */
+       ldapsvr_retire_query( server );
+
+       /* Test whether any queries for the same term exist */
+       qry = ldapsvr_locate_query( server, _searchTerm_ );
+       if( qry ) {
+               ItemFolder *folder = qry->folder;
+
+               /* Touch query to ensure it hangs around for a bit longer */            
+               ldapqry_touch( qry );
+               if( folder ) {
+                       addrindex_ldap_use_previous( folder, queryID );
+                       return;
+               }
+       }
+
+       /* Construct a query */
+       qry = ldapqry_create();
+       ldapqry_set_query_id( qry, queryID );
+       ldapqry_set_search_value( qry, _searchTerm_ );
+       ldapqry_set_query_type( qry, LDAPQUERY_DYNAMIC );
+       ldapqry_set_callback_entry( qry, addrindex_ldap_entry_cb );
+       ldapqry_set_callback_end( qry, addrindex_ldap_end_cb );
+
+       /* Name the query */
+       name = g_strdup_printf( "Search for '%s'", _searchTerm_ );
+       ldapqry_set_name( qry, name );
+       g_free( name );
+
+       ldapsvr_add_query( server, qry );
+       /* printf( "addrindex_search_ldap::executing dynamic search...\n" ); */
+       ldapsvr_execute_query( server, qry );
+}
+
+/**
+ * Construct an LDAP query and initiate an LDAP search.
+ * \param server      LDAP server object to search.
+ * \param searchTerm  Search term to locate.
+ * \param callbackEnd Function to call when search has terminated.
+ *
+ */
+void addrindex_search_ldap_noid(
+       LdapServer *server, const gchar *searchTerm, void * callbackEnd )
+{
+       LdapQuery *qry;
+       gchar *name;
+
+       /* Construct a query */
+       qry = ldapqry_create();
+       ldapqry_set_search_value( qry, searchTerm );
+       ldapqry_set_query_type( qry, LDAPQUERY_STATIC );
+       ldapqry_set_callback_end( qry, callbackEnd );
+
+       /* Name the query */
+       name = g_strdup_printf( "Static Search for '%s'", searchTerm );
+       ldapqry_set_name( qry, name );
+       g_free( name );
+
+       ldapsvr_add_query( server, qry );
+       /* printf( "addrindex_search_ldap_noid::executing static search...\n" ); */
+       ldapsvr_execute_query( server, qry );
+}
+#endif
+
+/**
+ * Perform the previously registered search.
+ * \param  addrIndex  Address index object.
+ * \param  queryID    ID of search query to be executed.
+ * \return <i>TRUE</i> if search started successfully, or <i>FALSE</i> if
+ *         failed.
+ */
+gboolean addrindex_start_search( AddressIndex *addrIndex, const gint queryID ) {
+       AddressInterface *iface;
+       AddressDataSource *ds;
+       AddressCache *cache;
+       GList *nodeIf;
+       GList *nodeDS;
+       gint type;
+
+       /* printf( "addrindex_start_search::%d::\n", queryID ); */
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+
+               if( ! iface->useInterface ) {
+                       continue;
+               }
+
+               type = iface->type;
+               nodeDS = iface->listSource;
+               while( nodeDS ) {
+                       ds = nodeDS->data;
+                       nodeDS = g_list_next( nodeDS );
+                       cache = NULL;
+
+                       if( type == ADDR_IF_BOOK ) {
+                               AddressBookFile *abf = ds->rawDataSource;
+                               cache = abf->addressCache;
+                       }
+                       else if( type == ADDR_IF_VCARD ) {
+                               VCardFile *vcf = ds->rawDataSource;
+                               cache = vcf->addressCache;
+                       }
+#ifdef USE_JPILOT
+                       else if( type == ADDR_IF_JPILOT ) {
+                               JPilotFile *jpf = ds->rawDataSource;
+                               cache = jpf->addressCache;
+                       }
+#endif
+#ifdef USE_LDAP
+                       else if( type == ADDR_IF_LDAP ) {
+                               LdapServer *server = ds->rawDataSource;
+                               addrindex_search_ldap( server, queryID );
+                       }
+#endif
+                       if( cache ) {
+                               addrindex_search_cache( cache, queryID );
+                       }
+               }
+       }
+       return TRUE;
+}
+
+/**
+ * Stop the previously registered search.
+ * \param addrIndex Address index object.
+ * \param queryID ID of search query to stop.
+ */
+void addrindex_stop_search( AddressIndex *addrIndex, const gint queryID ){
+#ifdef USE_LDAP
+       AddressInterface *iface;
+       AddressDataSource *ds;
+       GList *nodeIf;
+       GList *nodeDS;
+       gint type;
+
+       /* If query ID does not match, search has not been setup */
+       /* if( queryID != _queryID_ ) return; */
+
+       /* printf( "addrindex_stop_search::%d::\n", queryID ); */
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+
+               if( ! iface->useInterface ) {
+                       continue;
+               }
+
+               type = iface->type;
+               nodeDS = iface->listSource;
+               while( nodeDS ) {
+                       ds = nodeDS->data;
+                       nodeDS = g_list_next( nodeDS );
+                       if( type == ADDR_IF_LDAP ) {
+                               LdapServer *server = ds->rawDataSource;
+                               ldapsvr_stop_all_query( server );
+                       }
+               }
+       }
+#endif
+}
+
+/**
+ * Read all address books that do not support dynamic queries.
+ * \param addrIndex Address index object.
+ */
+void addrindex_read_all( AddressIndex *addrIndex ) {
+       AddressInterface *iface;
+       AddressDataSource *ds;
+       GList *nodeIf;
+       GList *nodeDS;
+
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+
+               if( ! iface->useInterface ) {
+                       continue;
+               }
+               if( iface->externalQuery ) {
+                       continue;
+               }
+               nodeDS = iface->listSource;
+               while( nodeDS ) {
+                       ds = nodeDS->data;
+                       nodeDS = g_list_next( nodeDS );
+
+                       /* Read address book */
+                       if( addrindex_ds_get_modify_flag( ds ) ) {
+                               addrindex_ds_read_data( ds );
+                               continue;
+                       }
+
+                       if( ! addrindex_ds_get_read_flag( ds ) ) {
+                               addrindex_ds_read_data( ds );
+                               continue;
+                       }
+               }
+       }
+       addrIndex->loadedFlag = TRUE;
+}
+
+/**
+ * Perform a simple search of all non-query type data sources for specified
+ * search term. If several entries are found, only the first item is
+ * returned. Interfaces that require a time-consuming "external query" are
+ * ignored for this search.
+ *
+ * \param  addrIndex  Address index object.
+ * \param  searchTerm Search term to find. Typically an email address.
+ * \return List of references to zero or mail E-Mail object that was found in
+ *         the address books, or <i>NULL</i> if nothing found. This list
+ *         *SHOULD* be freed when done.
+ */
+GList *addrindex_quick_search_list(
+               AddressIndex *addrIndex, const gchar *searchTerm )
+{
+       GList *listRet = NULL;
+       GList *listEMail;
+       AddressInterface *iface;
+       AddressDataSource *ds;
+       AddressCache *cache;
+       AddrCacheIndex *index;
+       ItemEMail *email;
+       GList *nodeIf;
+       GList *nodeDS;
+       GList *nodeEM;
+       gint type;
+
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+
+               if( ! iface->useInterface ) {
+                       /* Ignore interfaces that don't have a library */
+                       continue;
+               }
+               if( iface->externalQuery ) {
+                       /* Ignore interfaces that require a "query" */
+                       continue;
+               }
+
+               type = iface->type;
+               nodeDS = iface->listSource;
+               while( nodeDS ) {
+                       ds = nodeDS->data;
+                       nodeDS = g_list_next( nodeDS );
+                       cache = NULL;
+
+                       if( type == ADDR_IF_BOOK ) {
+                               AddressBookFile *abf = ds->rawDataSource;
+                               cache = abf->addressCache;
+                       }
+                       else if( type == ADDR_IF_VCARD ) {
+                               VCardFile *vcf = ds->rawDataSource;
+                               cache = vcf->addressCache;
+                       }
+#ifdef USE_JPILOT
+                       else if( type == ADDR_IF_JPILOT ) {
+                               JPilotFile *jpf = ds->rawDataSource;
+                               cache = jpf->addressCache;
+                       }
+#endif
+                       if( cache ) {
+                               index = cache->searchIndex;
+                               if( index == NULL ) {
+                                       continue;
+                               }
+                               if( index->invalid ) {
+                                       addrcache_build_index( cache );
+                               }
+                               listEMail = addrcindex_search( index, searchTerm );
+                               nodeEM = listEMail;
+                               while( nodeEM ) {
+                                       email = listEMail->data;
+                                       listRet = g_list_append( listRet, email );
+                                       nodeEM = g_list_next( nodeEM );
+                               }
+                               g_list_free( listEMail );
+                       }
+               }
+       }
+       return listRet;
+}
+
+/**
+ * Perform a simple search of all non-query type data sources for specified
+ * search term. If several entries are found, only the first item is
+ * returned. Interfaces that require a time-consuming "external query" are
+ * ignored for this search.
+ *
+ * \param  addrIndex  Address index object.
+ * \param  searchTerm Search term to find. Typically an email address.
+ * \return Reference to a single E-Mail object that was found in the address
+ *         book, or <i>NULL</i> if nothing found. This should *NOT* be freed
+ *         when done.
+ */
+ItemEMail *addrindex_quick_search_single(
+               AddressIndex *addrIndex, const gchar *searchTerm )
+{
+       ItemEMail *email = NULL;
+       AddressInterface *iface;
+       AddressDataSource *ds;
+       AddressCache *cache;
+       AddrCacheIndex *index;
+       GList *listEMail;
+       GList *nodeIf;
+       GList *nodeDS;
+       gint type;
+
+       /* printf( "addrindex_quick_search::%s::\n", searchTerm ); */
+       nodeIf = addrIndex->searchOrder;
+       while( nodeIf ) {
+               iface = nodeIf->data;
+               nodeIf = g_list_next( nodeIf );
+
+               if( ! iface->useInterface ) {
+                       continue;
+               }
+               if( iface->externalQuery ) {
+                       continue;
+               }
+
+               type = iface->type;
+               nodeDS = iface->listSource;
+               while( nodeDS ) {
+                       ds = nodeDS->data;
+                       nodeDS = g_list_next( nodeDS );
+                       cache = NULL;
+
+                       if( type == ADDR_IF_BOOK ) {
+                               AddressBookFile *abf = ds->rawDataSource;
+                               cache = abf->addressCache;
+                       }
+                       else if( type == ADDR_IF_VCARD ) {
+                               VCardFile *vcf = ds->rawDataSource;
+                               cache = vcf->addressCache;
+                       }
+#ifdef USE_JPILOT
+                       else if( type == ADDR_IF_JPILOT ) {
+                               JPilotFile *jpf = ds->rawDataSource;
+                               cache = jpf->addressCache;
+                       }
+#endif
+                       if( cache ) {
+                               index = cache->searchIndex;
+                               if( index == NULL ) {
+                                       continue;
+                               }
+                               if( index->invalid ) {
+                                       addrcache_build_index( cache );
+                               }
+
+                               listEMail = addrcindex_search( index, searchTerm );
+                               if( listEMail ) {
+                                       email = listEMail->data;
+                               }
+                               g_list_free( listEMail );
+                               if( email ) break;
+                       }
+               }
+       }
+       return email;
+}
+
+/*
+ * End of Source.
+ */
+
+
index 2c602c1..460daff 100644 (file)
@@ -28,6 +28,7 @@
 #include <glib.h>
 #include "addritem.h"
 #include "addrcache.h"
+#include "addrquery.h"
 
 #define ADDRESSBOOK_MAX_IFACE  4
 #define ADDRESSBOOK_INDEX_FILE "addrbook--index.xml"
@@ -56,6 +57,8 @@ struct _AddressIndex {
        gboolean dirtyFlag;
        GList *interfaceList;
        GHashTable *hashCache;
+       gboolean loadedFlag;
+       GList *searchOrder;
 };
 
 typedef struct _AddressInterface AddressInterface;
@@ -82,6 +85,10 @@ struct _AddressInterface {
        GList *(*getAllGroups)( void * );
        gchar *(*getName)( void * );
        void (*setAccessFlag)( void *, void * );
+       gboolean externalQuery;
+       gint searchOrder;
+       void (*startSearch)( void * );
+       void (*stopSearch)( void * );
 };
 
 typedef struct _AddressDataSource AddressDataSource;
@@ -99,6 +106,8 @@ void addrindex_set_file_name         ( AddressIndex *addrIndex,
                                          const gchar *value );
 void addrindex_set_dirty               ( AddressIndex *addrIndex,
                                          const gboolean value );
+gboolean addrindex_get_loaded          ( AddressIndex *addrIndex );
+
 GList *addrindex_get_interface_list    ( AddressIndex *addrIndex );
 void addrindex_free_index              ( AddressIndex *addrIndex );
 void addrindex_print_index             ( AddressIndex *addrIndex, FILE *stream );
@@ -144,6 +153,22 @@ gboolean addrindex_ds_get_readonly ( AddressDataSource *ds );
 GList *addrindex_ds_get_all_persons    ( AddressDataSource *ds );
 GList *addrindex_ds_get_all_groups     ( AddressDataSource *ds );
 
+/* Search support */
+gint addrindex_setup_search    ( AddressIndex *addrIndex,
+                                 const gchar *searchTerm,
+                                 const gpointer target,
+                                 AddrSearchCallbackFunc callBack );
+gboolean addrindex_start_search        ( AddressIndex *addrIndex,
+                                 const gint queryID );
+void addrindex_stop_search     ( AddressIndex *addrIndex,
+                                 const gint queryID );
+
+void addrindex_read_all                        ( AddressIndex *addrIndex );
+GList *addrindex_quick_search_list     ( AddressIndex *addrIndex,
+                                         const gchar *searchTerm );
+ItemEMail *addrindex_quick_search_single( AddressIndex *addrIndex,
+                                         const gchar *searchTerm );
+
 #endif /* __ADDRINDEX_H__ */
 
 /*
index bc0d4a9..820e2e6 100644 (file)
@@ -778,6 +778,8 @@ ItemFolder *addritem_create_item_folder( void ) {
        folder->listFolder = NULL;
        folder->listPerson = NULL;
        folder->listGroup = NULL;
+       folder->folderType = ADDRFOLDER_NONE;
+       folder->folderData = NULL;
        return folder;
 }
 
@@ -793,6 +795,7 @@ ItemFolder *addritem_copy_item_folder( ItemFolder *item ) {
        if( item ) {
                itemNew = addritem_create_item_folder();
                ADDRITEM_NAME(itemNew) = g_strdup( ADDRITEM_NAME(item) );
+               itemNew->folderType = item->folderType;
        }
        return itemNew;
 }
@@ -836,6 +839,8 @@ void addritem_free_item_folder( ItemFolder *folder ) {
        folder->listFolder = NULL;
        folder->listGroup = NULL;
        folder->listPerson = NULL;
+       folder->folderType = ADDRFOLDER_NONE;
+       folder->folderData = NULL;
 
        g_free( folder );
 }
@@ -933,6 +938,7 @@ void addritem_print_item_folder( ItemFolder *folder, FILE *stream ) {
        fprintf( stream, "\tsub: %d\n", ADDRITEM_SUBTYPE(folder) );
        fprintf( stream, "\tnam: '%s'\n", ADDRITEM_NAME(folder) );
        fprintf( stream, "\trem: '%s'\n", folder->remarks );
+       fprintf( stream, "\ttyp: %d\n", folder->folderType );
        fprintf( stream, "\t---\n" );
        parent = ( ItemFolder * ) ADDRITEM_PARENT(folder);
        if( parent ) {
index df4ca39..fdf0cf7 100644 (file)
@@ -45,6 +45,14 @@ typedef enum {
        ITEMTYPE_DATASOURCE
 } ItemObjectType;
 
+typedef enum {
+       ADDRFOLDER_NONE,
+       ADDRFOLDER_ROOT,
+       ADDRFOLDER_REGULAR,
+       ADDRFOLDER_CATEGORY,
+       ADDRFOLDER_LDAP_QUERY
+} AddressFolderType;
+
 typedef struct _AddrItemObject AddrItemObject;
 struct _AddrItemObject {
        ItemObjectType type;
@@ -89,6 +97,8 @@ struct _ItemFolder {
        GList    *listFolder;   /* List of contained (child) folders */
        GList    *listPerson;   /* List of contained persons */
        GList    *listGroup;    /* List of contained (child) groups */
+       AddressFolderType folderType;   /* Folder type */
+       gpointer *folderData;           /* Pointer to folder's data */
 };
 
 typedef struct _ItemGroup ItemGroup;
diff --git a/src/addrquery.h b/src/addrquery.h
new file mode 100644 (file)
index 0000000..aa68aab
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions to define an address query (a request).
+ */
+
+#ifndef __ADDRQUERY_H__
+#define __ADDRQUERY_H__
+
+#include <glib.h>
+// #include <stdio.h>
+
+/* Address search call back function */
+typedef gint ( AddrSearchCallbackFunc ) ( gint cacheID,
+                                         GList *listEMail,
+                                         gpointer target );
+
+#endif /* __ADDRQUERY_H__ */
+
+/*
+ * End of Source.
+ */
index 584b9bf..c6da1f1 100644 (file)
@@ -1030,6 +1030,7 @@ ItemPerson *addressbook_edit_person( AddressBookFile *abf, ItemFolder *parent, I
                g_free( name );
        }
        g_free( cn );
+       addrbook_invalidate( abf );
 
        gtk_clist_clear( GTK_CLIST(personeditdlg.clist_email) );
        gtk_clist_clear( GTK_CLIST(personeditdlg.clist_attrib) );
index 48877b5..cd0423b 100644 (file)
@@ -482,6 +482,7 @@ ItemGroup *addressbook_edit_group( AddressBookFile *abf, ItemFolder *parent, Ite
        name = gtk_editable_get_chars( GTK_EDITABLE(groupeditdlg.entry_name), 0, -1 );
        addritem_group_set_name( group, name );
        g_free( name );
+       addrbook_invalidate( abf );
 
        listEMail = NULL;
        return group;
@@ -525,6 +526,7 @@ ItemFolder *addressbook_edit_folder( AddressBookFile *abf, ItemFolder *parent, I
        }
        addritem_folder_set_name( folder, name );
        g_free( name );
+       addrbook_invalidate( abf );
        return folder;
 }
 
index 2e5e552..212c01b 100644 (file)
 #include "prefs_common.h"
 #include "addressitem.h"
 #include "mgutils.h"
-#include "syldap.h"
+#include "ldapserver.h"
+#include "ldapctrl.h"
+#include "ldaputil.h"
 #include "editldap_basedn.h"
 #include "manage_window.h"
 #include "gtkutils.h"
 
+#define PAGE_BASIC      0
+#define PAGE_SEARCH     1
+#define PAGE_EXTENDED   2
+
 #define ADDRESSBOOK_GUESS_LDAP_NAME    "MyServer"
 #define ADDRESSBOOK_GUESS_LDAP_SERVER  "localhost"
 
@@ -68,10 +74,52 @@ static struct _LDAPEdit {
        GtkWidget *spinbtn_timeout;
        GtkWidget *entry_bindDN;
        GtkWidget *entry_bindPW;
-       GtkWidget *entry_criteria;
        GtkWidget *spinbtn_maxentry;
+       GtkWidget *entry_criteria;
+       GtkWidget *spinbtn_queryage;
+       GtkWidget *check_dynsearch;
 } ldapedit;
 
+/**
+ * Parse out individual attribute names from criteria string.
+ * \param criteria Criteria string.
+ * \ctl   Control object.
+ */
+static gboolean editldap_validate_criteria( gchar *criteria ) {
+       gchar *ptr;
+       gchar **splitStr;
+       gint i;
+       gboolean errorFlag;
+
+       errorFlag = TRUE;
+
+       /* Replace delimiters with spaces */
+       ptr = criteria;
+       while( *ptr ) {
+               if( *ptr == ',' || *ptr == ';' || *ptr == '|' )
+                       *ptr = ' ';
+               ptr++;
+       }
+
+       /* Parse string */
+       splitStr = g_strsplit( criteria, " ", 0 );
+       i = 0;
+       while( TRUE ) {
+               if( splitStr[i] ) {
+                       if( *splitStr[i] ) {
+                               errorFlag = FALSE;
+                               break;
+                       }
+               }
+               else {
+                       break;
+               }
+               i++;
+       }
+       g_strfreev( splitStr );
+       return errorFlag;
+}
+
 /*
 * Edit functions.
 */
@@ -79,14 +127,67 @@ static void edit_ldap_status_show( gchar *msg ) {
        if( ldapedit.statusbar != NULL ) {
                gtk_statusbar_pop( GTK_STATUSBAR(ldapedit.statusbar), ldapedit.status_cid );
                if( msg ) {
-                       gtk_statusbar_push( GTK_STATUSBAR(ldapedit.statusbar), ldapedit.status_cid, msg );
+                       gtk_statusbar_push( GTK_STATUSBAR(ldapedit.statusbar),
+                               ldapedit.status_cid, msg );
+               }
+       }
+}
+
+static gboolean edit_ldap_validate( void ) {
+       gchar *str;
+       gboolean errorFlag;
+       gint page;
+
+       errorFlag = FALSE;
+       str = gtk_editable_get_chars(
+                       GTK_EDITABLE(ldapedit.entry_name), 0, -1 );
+       if( *str == '\0' ) {
+               page = PAGE_BASIC;
+               gtk_widget_grab_focus( ldapedit.entry_name );
+               edit_ldap_status_show( _( "A Name must be supplied." ) );
+               errorFlag = TRUE;
+       }
+       g_free( str );
+
+       if( ! errorFlag ) {
+               str = gtk_editable_get_chars(
+                               GTK_EDITABLE(ldapedit.entry_server), 0, -1 );
+               if( *str == '\0' ) {
+                       page = PAGE_BASIC;
+                       gtk_widget_grab_focus( ldapedit.entry_server );
+                       edit_ldap_status_show(
+                               _( "A Hostname must be supplied for the server." ) );
+                       errorFlag = TRUE;
+               }
+               g_free( str );
+       }
+
+       if( ! errorFlag ) {
+               str = gtk_editable_get_chars(
+                               GTK_EDITABLE(ldapedit.entry_criteria), 0, -1 );
+               if( editldap_validate_criteria( str ) ) {
+                       page = PAGE_SEARCH;
+                       gtk_widget_grab_focus( ldapedit.entry_criteria );
+                       edit_ldap_status_show(
+                               _( "At least one LDAP search attribute should be supplied." ) );
+                       errorFlag = TRUE;
                }
+               g_free( str );
+       }
+
+       /* Switch to page with error */
+       if( errorFlag ) {
+               gtk_notebook_set_page( GTK_NOTEBOOK(ldapedit.notebook), page );
        }
+
+       return errorFlag;
 }
 
 static void edit_ldap_ok( GtkWidget *widget, gboolean *cancelled ) {
-       *cancelled = FALSE;
-       gtk_main_quit();
+       if( ! edit_ldap_validate() ) {
+               *cancelled = FALSE;
+               gtk_main_quit();
+       }
 }
 
 static void edit_ldap_cancel( GtkWidget *widget, gboolean *cancelled ) {
@@ -107,10 +208,6 @@ static void edit_ldap_key_pressed( GtkWidget *widget, GdkEventKey *event, gboole
        }
 }
 
-static void edit_ldap_switch_page( GtkWidget *widget ) {
-       edit_ldap_status_show( "" );
-}
-
 static void edit_ldap_server_check( void ) {
        gchar *sHost, *sBind, *sPass;
        gint iPort, iTime;
@@ -118,6 +215,7 @@ static void edit_ldap_server_check( void ) {
        gchar *sBaseDN = NULL;
        gint iBaseDN = 0;
        gboolean flg;
+       GList *baseDN = NULL;
 
        edit_ldap_status_show( "" );
        flg = FALSE;
@@ -131,9 +229,9 @@ static void edit_ldap_server_check( void ) {
        g_strchomp( sPass ); g_strchug( sPass );
        if( *sHost != '\0' ) {
                /* Test connection to server */
-               if( syldap_test_connect_s( sHost, iPort ) ) {
+               if( ldaputil_test_connect( sHost, iPort ) ) {
                        /* Attempt to read base DN */
-                       GList *baseDN = syldap_read_basedn_s( sHost, iPort, sBind, sPass, iTime );
+                       baseDN = ldaputil_read_basedn( sHost, iPort, sBind, sPass, iTime );
                        if( baseDN ) {
                                GList *node = baseDN;
                                while( node ) {
@@ -196,7 +294,7 @@ static void edit_ldap_basedn_select( void ) {
 }
 
 static void edit_ldap_search_reset( void ) {
-       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), SYLDAP_DFL_CRITERIA );
+       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), LDAPCTL_DFL_ATTR_LIST );
 }
 
 static void addressbook_edit_ldap_dialog_create( gboolean *cancelled ) {
@@ -223,7 +321,6 @@ static void addressbook_edit_ldap_dialog_create( gboolean *cancelled ) {
                           cancelled);
 
        vbox = gtk_vbox_new( FALSE, 6 );
-       /* gtk_container_set_border_width(GTK_CONTAINER(vbox), BORDER_WIDTH); */
        gtk_widget_show( vbox );
        gtk_container_add( GTK_CONTAINER( window ), vbox );
 
@@ -249,8 +346,6 @@ static void addressbook_edit_ldap_dialog_create( gboolean *cancelled ) {
                           GTK_SIGNAL_FUNC(edit_ldap_ok), cancelled);
        gtk_signal_connect(GTK_OBJECT(cancel_btn), "clicked",
                           GTK_SIGNAL_FUNC(edit_ldap_cancel), cancelled);
-       gtk_signal_connect(GTK_OBJECT(notebook), "switch_page",
-                          GTK_SIGNAL_FUNC(edit_ldap_switch_page), NULL );
 
        gtk_widget_show_all(vbox);
 
@@ -259,10 +354,12 @@ static void addressbook_edit_ldap_dialog_create( gboolean *cancelled ) {
        ldapedit.ok_btn     = ok_btn;
        ldapedit.cancel_btn = cancel_btn;
        ldapedit.statusbar  = statusbar;
-       ldapedit.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit LDAP Server Dialog" );
+       ldapedit.status_cid =
+               gtk_statusbar_get_context_id(
+                       GTK_STATUSBAR(statusbar), "Edit LDAP Server Dialog" );
 }
 
-void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
+static void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        GtkWidget *vbox;
        GtkWidget *table;
        GtkWidget *label;
@@ -279,7 +376,6 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        vbox = gtk_vbox_new( FALSE, 8 );
        gtk_widget_show( vbox );
        gtk_container_add( GTK_CONTAINER( ldapedit.notebook ), vbox );
-       /* gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); */
 
        label = gtk_label_new( pageLbl );
        gtk_widget_show( label );
@@ -300,7 +396,8 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_name = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        /* Next row */
        ++top;
@@ -309,7 +406,8 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_server = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_server, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_server, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        /* Next row */
        ++top;
@@ -323,7 +421,8 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_port, FALSE, FALSE, 0);
        gtk_widget_set_usize (spinbtn_port, 64, -1);
        gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_port), TRUE);
-       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        check_btn = gtk_button_new_with_label( _(" Check Server "));
        gtk_table_attach(GTK_TABLE(table), check_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0);
@@ -335,16 +434,19 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_baseDN = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_baseDN, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_baseDN, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        lookdn_btn = gtk_button_new_with_label( _(" ... "));
        gtk_table_attach(GTK_TABLE(table), lookdn_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0);
 
+       /* Signal handlers */
        gtk_signal_connect(GTK_OBJECT(check_btn), "clicked",
                           GTK_SIGNAL_FUNC(edit_ldap_server_check), NULL);
        gtk_signal_connect(GTK_OBJECT(lookdn_btn), "clicked",
                           GTK_SIGNAL_FUNC(edit_ldap_basedn_select), NULL);
 
+       /* Done */
        gtk_widget_show_all(vbox);
 
        ldapedit.entry_name   = entry_name;
@@ -353,25 +455,21 @@ void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) {
        ldapedit.entry_baseDN = entry_baseDN;
 }
 
-void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
+static void addressbook_edit_ldap_page_search( gint pageNum, gchar *pageLbl ) {
        GtkWidget *vbox;
        GtkWidget *table;
        GtkWidget *label;
-       GtkWidget *entry_bindDN;
-       GtkWidget *entry_bindPW;
        GtkWidget *entry_criteria;
        GtkWidget *hbox_spin;
-       GtkObject *spinbtn_timeout_adj;
-       GtkWidget *spinbtn_timeout;
-       GtkObject *spinbtn_maxentry_adj;
-       GtkWidget *spinbtn_maxentry;
+       GtkObject *spinbtn_queryage_adj;
+       GtkWidget *spinbtn_queryage;
+       GtkWidget *check_dynsearch;
        GtkWidget *reset_btn;
        gint top;
 
        vbox = gtk_vbox_new( FALSE, 8 );
        gtk_widget_show( vbox );
        gtk_container_add( GTK_CONTAINER( ldapedit.notebook ), vbox );
-       /* gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); */
 
        label = gtk_label_new( pageLbl );
        gtk_widget_show( label );
@@ -387,24 +485,90 @@ void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
 
        /* First row */
        top = 0;
-       label = gtk_label_new(_("Search Criteria"));
+       label = gtk_label_new(_("Search Attributes"));
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0);
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_criteria = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_criteria, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_criteria, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
-       reset_btn = gtk_button_new_with_label( _(" Reset "));
+       reset_btn = gtk_button_new_with_label( _(" Defaults "));
        gtk_table_attach(GTK_TABLE(table), reset_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0);
 
        /* Next row */
        ++top;
+       label = gtk_label_new(_("Max Query Age (secs)"));
+       gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0);
+       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+
+       hbox_spin = gtk_hbox_new (FALSE, 8);
+       spinbtn_queryage_adj = gtk_adjustment_new(
+               LDAPCTL_DFL_QUERY_AGE, 1, LDAPCTL_MAX_QUERY_AGE, 10, 1000, 1000 );
+       spinbtn_queryage = gtk_spin_button_new(GTK_ADJUSTMENT (spinbtn_queryage_adj), 1, 0);
+       gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_queryage, FALSE, FALSE, 0);
+       gtk_widget_set_usize (spinbtn_queryage, 64, -1);
+       gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_queryage), TRUE);
+       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+
+       /* Next row */
+       ++top;
+       check_dynsearch = gtk_check_button_new_with_label(
+                               _("Include server in dynamic search") );
+       gtk_table_attach(GTK_TABLE(table), check_dynsearch, 1, 3, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+
+       /* Signal handlers */
+       gtk_signal_connect(GTK_OBJECT(reset_btn), "clicked",
+                          GTK_SIGNAL_FUNC(edit_ldap_search_reset), NULL);
+
+       /* Done */
+       gtk_widget_show_all(vbox);
+
+       ldapedit.entry_criteria   = entry_criteria;
+       ldapedit.spinbtn_queryage = spinbtn_queryage;
+       ldapedit.check_dynsearch  = check_dynsearch;
+}
+
+static void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
+       GtkWidget *vbox;
+       GtkWidget *table;
+       GtkWidget *label;
+       GtkWidget *entry_bindDN;
+       GtkWidget *entry_bindPW;
+       GtkWidget *hbox_spin;
+       GtkObject *spinbtn_timeout_adj;
+       GtkWidget *spinbtn_timeout;
+       GtkObject *spinbtn_maxentry_adj;
+       GtkWidget *spinbtn_maxentry;
+       gint top;
+
+       vbox = gtk_vbox_new( FALSE, 8 );
+       gtk_widget_show( vbox );
+       gtk_container_add( GTK_CONTAINER( ldapedit.notebook ), vbox );
+
+       label = gtk_label_new( pageLbl );
+       gtk_widget_show( label );
+       gtk_notebook_set_tab_label(
+               GTK_NOTEBOOK( ldapedit.notebook ),
+               gtk_notebook_get_nth_page( GTK_NOTEBOOK( ldapedit.notebook ), pageNum ), label );
+
+       table = gtk_table_new( LDAPEDIT_TABLE_ROWS, LDAPEDIT_TABLE_COLS, FALSE);
+       gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+       gtk_container_set_border_width( GTK_CONTAINER(table), 8 );
+       gtk_table_set_row_spacings(GTK_TABLE(table), 8);
+       gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+
+       /* Next row */
+       top = 0;
        label = gtk_label_new(_("Bind DN"));
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0);
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_bindDN = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_bindDN, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_bindDN, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        /* Next row */
        ++top;
@@ -413,7 +577,8 @@ void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 
        entry_bindPW = gtk_entry_new();
-       gtk_table_attach(GTK_TABLE(table), entry_bindPW, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), entry_bindPW, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        /* Next row */
        ++top;
@@ -427,7 +592,8 @@ void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
        gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_timeout, FALSE, FALSE, 0);
        gtk_widget_set_usize (spinbtn_timeout, 64, -1);
        gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_timeout), TRUE);
-       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
+       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
        /* Next row */
        ++top;
@@ -441,14 +607,12 @@ void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) {
        gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_maxentry, FALSE, FALSE, 0);
        gtk_widget_set_usize (spinbtn_maxentry, 64, -1);
        gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_maxentry), TRUE);
-       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
-
-       gtk_signal_connect(GTK_OBJECT(reset_btn), "clicked",
-                          GTK_SIGNAL_FUNC(edit_ldap_search_reset), NULL);
+       gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1),
+               GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
 
+       /* Done */
        gtk_widget_show_all(vbox);
 
-       ldapedit.entry_criteria   = entry_criteria;
        ldapedit.entry_bindDN     = entry_bindDN;
        ldapedit.entry_bindPW     = entry_bindPW;
        ldapedit.spinbtn_timeout  = spinbtn_timeout;
@@ -459,6 +623,7 @@ static void addressbook_edit_ldap_create( gboolean *cancelled ) {
        gint page = 0;
        addressbook_edit_ldap_dialog_create( cancelled );
        addressbook_edit_ldap_page_basic( page++, _( "Basic" ) );
+       addressbook_edit_ldap_page_search( page++, _( "Search" ) );
        addressbook_edit_ldap_page_extended( page++, _( "Extended" ) );
        gtk_widget_show_all( ldapedit.window );
 }
@@ -496,17 +661,155 @@ gint edit_ldap_get_optmenu( GtkOptionMenu *optmenu ) {
        return GPOINTER_TO_INT(gtk_object_get_user_data(GTK_OBJECT(menuitem)));
 }
 
+/**
+ * Format criteria list for display.
+ * \param ctl Control object.
+ * \return Formatted string, or <i>NULL</i> if no attributes found.
+ */
+static gchar *editldap_build_criteria_list( const LdapControl *ctl ) {
+       gchar *str = NULL;
+       gchar *tmp = NULL;
+       GList *node;
+
+       node = ldapctl_get_criteria_list( ctl );
+       while( node ) {
+               gchar *attr = node->data;
+               if( str ) {
+                       tmp = g_strdup_printf( "%s, %s", str, attr );
+                       g_free( str );
+                       str = tmp;
+                       tmp = NULL;
+               }
+               else {
+                       str = g_strdup( attr );
+               }
+               node = g_list_next( node );
+       }
+
+       return str;
+}
+
+/**
+ * Parse out individual attribute names from criteria string.
+ * \param criteria Criteria string.
+ * \ctl   Control object.
+ */
+static void editldap_parse_criteria( gchar *criteria, LdapControl *ctl ) {
+       gchar *ptr;
+       gchar **splitStr;
+       gint i;
+
+       /* Replace delimiters with spaces */
+       ptr = criteria;
+       while( *ptr ) {
+               if( *ptr == ',' || *ptr == ';' || *ptr == '|' )
+                       *ptr = ' ';
+               ptr++;
+       }
+
+       /* Parse string */
+       ldapctl_criteria_list_clear( ctl );
+       splitStr = g_strsplit( criteria, " ", 0 );
+       i = 0;
+       while( TRUE ) {
+               if( splitStr[i] ) {
+                       if( *splitStr[i] ) {
+                               ldapctl_criteria_list_add( ctl, splitStr[i] );
+                       }
+               }
+               else {
+                       break;
+               }
+               i++;
+       }
+       g_strfreev( splitStr );
+}
+
+/**
+ * Clear entry fields to reasonable defaults (for a new server entry).
+ */
+static void edit_ldap_clear_fields( void ) {
+       gtk_entry_set_text(
+               GTK_ENTRY(ldapedit.entry_name), ADDRESSBOOK_GUESS_LDAP_NAME );
+       gtk_entry_set_text(
+               GTK_ENTRY(ldapedit.entry_server), ADDRESSBOOK_GUESS_LDAP_SERVER );
+       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), "");
+       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindDN), "");
+       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindPW), "");
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON( ldapedit.spinbtn_port ), LDAPCTL_DFL_PORT );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ), LDAPCTL_DFL_TIMEOUT );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ), LDAPCTL_DFL_TIMEOUT );
+       gtk_entry_set_text(
+               GTK_ENTRY(ldapedit.entry_criteria), LDAPCTL_DFL_ATTR_LIST );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON(ldapedit.spinbtn_queryage), LDAPCTL_DFL_QUERY_AGE );
+       gtk_toggle_button_set_active(
+               GTK_TOGGLE_BUTTON( ldapedit.check_dynsearch), TRUE );
+}
+
+/**
+ * Load entry fields from server control data.
+ * \param server Server object.
+ */
+static void edit_ldap_set_fields( LdapServer *server ) {
+       LdapControl *ctl;
+       gchar *crit;
+
+       if( ldapsvr_get_name( server ) )
+               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_name),
+               ldapsvr_get_name( server ) );
+
+       ctl = server->control;
+       if( ctl->hostName )
+               gtk_entry_set_text(
+                       GTK_ENTRY(ldapedit.entry_server), ctl->hostName);
+       if( ctl->baseDN )
+               gtk_entry_set_text(
+                       GTK_ENTRY(ldapedit.entry_baseDN), ctl->baseDN );
+       if( ctl->bindDN )
+               gtk_entry_set_text(
+                       GTK_ENTRY(ldapedit.entry_bindDN), ctl->bindDN );
+       if( ctl->bindPass )
+               gtk_entry_set_text(
+                       GTK_ENTRY(ldapedit.entry_bindPW), ctl->bindPass );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON(ldapedit.spinbtn_port), ctl->port );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON(ldapedit.spinbtn_timeout), ctl->timeOut );
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON(ldapedit.spinbtn_maxentry), ctl->maxEntries );
+
+       /* Format criteria */
+       crit = editldap_build_criteria_list( ctl );
+       if( crit ) {
+               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), crit );
+               g_free( crit );
+       }
+       else {
+               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), "" );
+       }
+       gtk_spin_button_set_value(
+               GTK_SPIN_BUTTON(ldapedit.spinbtn_queryage), ctl->maxQueryAge );
+       gtk_toggle_button_set_active(
+               GTK_TOGGLE_BUTTON( ldapedit.check_dynsearch), server->searchFlag );
+}
+
 AdapterDSource *addressbook_edit_ldap( AddressIndex *addrIndex, AdapterDSource *ads ) {
        static gboolean cancelled;
        gchar *sName, *sHost, *sBase, *sBind, *sPass, *sCrit;
-       gint iPort, iMaxE, iTime;
+       gint iPort, iMaxE, iTime, iAge;
+       gboolean bSrch;
        AddressDataSource *ds = NULL;
-       SyldapServer *server = NULL;
+       LdapServer *server = NULL;
+       LdapControl *ctl = NULL;
        gboolean fin;
 
        if (!ldapedit.window)
                addressbook_edit_ldap_create(&cancelled);
-       gtk_notebook_set_page( GTK_NOTEBOOK(ldapedit.notebook), 0 );
+       gtk_notebook_set_page( GTK_NOTEBOOK(ldapedit.notebook), PAGE_BASIC );
        gtk_widget_grab_focus(ldapedit.ok_btn);
        gtk_widget_grab_focus(ldapedit.entry_name);
        gtk_widget_show(ldapedit.window);
@@ -516,72 +819,60 @@ AdapterDSource *addressbook_edit_ldap( AddressIndex *addrIndex, AdapterDSource *
        if( ads ) {
                ds = ads->dataSource;
                server = ds->rawDataSource;
-               if ( syldap_get_name( server ) )
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_name),
-                               syldap_get_name( server ) );
-               if (server->hostName)
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_server), server->hostName);
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ), server->port );
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ), server->timeOut );
-               if (server->baseDN)
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), server->baseDN);
-               if (server->searchCriteria)
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), server->searchCriteria);
-               if (server->bindDN)
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindDN), server->bindDN);
-               if (server->bindPass)
-                       gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindPW), server->bindPass);
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ), server->maxEntries );
-               gtk_window_set_title( GTK_WINDOW(ldapedit.window), _("Edit LDAP Server"));
+               edit_ldap_set_fields( server );
+               gtk_window_set_title(
+                       GTK_WINDOW(ldapedit.window), _("Edit LDAP Server"));
        }
        else {
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_name), ADDRESSBOOK_GUESS_LDAP_NAME );
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_server), ADDRESSBOOK_GUESS_LDAP_SERVER );
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ), SYLDAP_DFL_PORT );
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ), SYLDAP_DFL_TIMEOUT );
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), "");
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), SYLDAP_DFL_CRITERIA );
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindDN), "");
-               gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindPW), "");
-               gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ), SYLDAP_MAX_ENTRIES );
-               gtk_window_set_title( GTK_WINDOW(ldapedit.window), _("Add New LDAP Server"));
+               edit_ldap_clear_fields();
+               gtk_window_set_title(
+                       GTK_WINDOW(ldapedit.window), _("Add New LDAP Server"));
        }
 
        gtk_main();
        gtk_widget_hide(ldapedit.window);
        if (cancelled == TRUE) return NULL;
 
-       fin = FALSE;
        sName = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_name), 0, -1 );
        sHost = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_server), 0, -1 );
-       iPort = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ) );
-       iTime = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ) );
        sBase = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_baseDN), 0, -1 );
        sCrit = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_criteria), 0, -1 );
        sBind = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindDN), 0, -1 );
        sPass = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindPW), 0, -1 );
+       iPort = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ) );
+       iTime = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ) );
        iMaxE = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ) );
+       iAge  = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_queryage ) );
+       bSrch = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( ldapedit.check_dynsearch ) );
 
+       fin = FALSE;
        if( *sName == '\0' ) fin = TRUE;
        if( *sHost == '\0' ) fin = TRUE;
-       if( *sBase == '\0' ) fin = TRUE;
 
        if( ! fin ) {
+               /* Save changes */
                if( ! ads ) {
-                       server = syldap_create();
+                       /* New server */
+                       server = ldapsvr_create();
                        ds = addrindex_index_add_datasource( addrIndex, ADDR_IF_LDAP, server );
                        ads = addressbook_create_ds_adapter( ds, ADDR_LDAP, NULL );
                }
+               ctl = server->control;
                addressbook_ads_set_name( ads, sName );
-               syldap_set_name( server, sName );
-               syldap_set_host( server, sHost );
-               syldap_set_port( server, iPort );
-               syldap_set_base_dn( server, sBase );
-               syldap_set_bind_dn( server, sBind );
-               syldap_set_bind_password( server, sPass );
-               syldap_set_search_criteria( server, sCrit );
-               syldap_set_max_entries( server, iMaxE );
-               syldap_set_timeout( server, iTime );
+               ldapsvr_set_name( server, sName );
+               ldapsvr_set_search_flag( server, bSrch );
+               ldapctl_set_host( ctl, sHost );
+               ldapctl_set_base_dn( ctl, sBase );
+               ldapctl_set_bind_dn( ctl, sBind );
+               ldapctl_set_bind_password( ctl, sPass );
+               ldapctl_set_port( ctl, iPort );
+               ldapctl_set_max_entries( ctl, iMaxE );
+               ldapctl_set_timeout( ctl, iTime );
+               ldapctl_set_max_query_age( ctl, iAge );
+
+               /* Save attributes */
+               editldap_parse_criteria( sCrit, ctl );
+
        }
        g_free( sName );
        g_free( sHost );
index 6cb32e9..6657fe5 100644 (file)
@@ -41,7 +41,7 @@
 
 #include "intl.h"
 #include "prefs_common.h"
-#include "syldap.h"
+#include "ldaputil.h"
 #include "mgutils.h"
 #include "gtkutils.h"
 #include "manage_window.h"
@@ -237,16 +237,21 @@ static void edit_ldap_bdn_create(void) {
        ldapedit_basedn.ok_btn     = ok_btn;
        ldapedit_basedn.cancel_btn = cancel_btn;
        ldapedit_basedn.statusbar  = statusbar;
-       ldapedit_basedn.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit LDAP Select Base DN" );
+       ldapedit_basedn.status_cid =
+               gtk_statusbar_get_context_id(
+                       GTK_STATUSBAR(statusbar), "Edit LDAP Select Base DN" );
 }
 
-void edit_ldap_bdn_load_data( const gchar *hostName, const gint iPort, const gint tov, const gchar* bindDN,
-              const gchar *bindPW ) {
+void edit_ldap_bdn_load_data(
+       const gchar *hostName, const gint iPort, const gint tov,
+       const gchar* bindDN, const gchar *bindPW )
+{
        gchar *sHost;
        gchar *sMsg = NULL;
        gchar sPort[20];
        gboolean flgConn;
        gboolean flgDN;
+       GList *baseDN = NULL;
 
        edit_ldap_bdn_status_show( "" );
        gtk_clist_clear(GTK_CLIST(ldapedit_basedn.basedn_list));
@@ -258,9 +263,9 @@ void edit_ldap_bdn_load_data( const gchar *hostName, const gint iPort, const gin
        gtk_label_set_text(GTK_LABEL(ldapedit_basedn.port_label), sPort);
        if( *sHost != '\0' ) {
                /* Test connection to server */
-               if( syldap_test_connect_s( sHost, iPort ) ) {
+               if( ldaputil_test_connect( sHost, iPort ) ) {
                        /* Attempt to read base DN */
-                       GList *baseDN = syldap_read_basedn_s( sHost, iPort, bindDN, bindPW, tov );
+                       baseDN = ldaputil_read_basedn( sHost, iPort, bindDN, bindPW, tov );
                        if( baseDN ) {
                                GList *node = baseDN;
                                gchar *text[2] = { NULL, NULL };
index 5d86f87..4797a04 100644 (file)
@@ -165,6 +165,10 @@ JPilotFile *jpilot_create() {
        pilotFile->labelInd = NULL;
        pilotFile->havePC3 = FALSE;
        pilotFile->pc3ModifyTime = 0;
+
+       /* We want to use an address completion index */
+       addrcache_use_index( pilotFile->addressCache, TRUE );
+
        return pilotFile;
 }
 
@@ -404,7 +408,6 @@ void jpilot_free( JPilotFile *pilotFile ) {
        jpilot_clear_custom_labels( pilotFile );
 
        /* Clear cache */
-       addrcache_clear( pilotFile->addressCache );
        addrcache_free( pilotFile->addressCache );
 
        /* Free internal stuff */
@@ -1612,6 +1615,9 @@ gint jpilot_read_data( JPilotFile *pilotFile ) {
                        if( pilotFile->retVal == MGU_SUCCESS ) {
                                pilotFile->addressCache->modified = FALSE;
                                pilotFile->addressCache->dataRead = TRUE;
+
+                               /* Build address completion index */
+                               addrcache_build_index( pilotFile->addressCache );
                        }
                }
        }
diff --git a/src/ldapctrl.c b/src/ldapctrl.c
new file mode 100644 (file)
index 0000000..a25d063
--- /dev/null
@@ -0,0 +1,528 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions for LDAP control data.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <sys/time.h>
+#include <string.h>
+
+#include "ldapctrl.h"
+#include "mgutils.h"
+
+/**
+ * Create new LDAP control block object.
+ * \return Initialized control object.
+ */
+LdapControl *ldapctl_create( void ) {
+       LdapControl *ctl;
+
+       ctl = g_new0( LdapControl, 1 );
+       ctl->hostName = NULL;
+       ctl->port = LDAPCTL_DFL_PORT;
+       ctl->baseDN = NULL;
+       ctl->bindDN = NULL;
+       ctl->bindPass = NULL;
+       ctl->listCriteria = NULL;
+       ctl->attribEMail = g_strdup( LDAPCTL_ATTR_EMAIL );
+       ctl->attribCName = g_strdup( LDAPCTL_ATTR_COMMONNAME );
+       ctl->attribFName = g_strdup( LDAPCTL_ATTR_GIVENNAME );
+       ctl->attribLName = g_strdup( LDAPCTL_ATTR_SURNAME );
+       ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
+       ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
+       ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
+
+       /* Mutex to protect control block */
+       ctl->mutexCtl = g_malloc0( sizeof( pthread_mutex_t ) );
+       pthread_mutex_init( ctl->mutexCtl, NULL );
+
+       return ctl;
+}
+
+/**
+ * Specify hostname to be used.
+ * \param ctl   Control object to process.
+ * \param value Host name.
+ */
+void ldapctl_set_host( LdapControl* ctl, const gchar *value ) {
+       ctl->hostName = mgu_replace_string( ctl->hostName, value );
+       g_strstrip( ctl->hostName );
+}
+
+/**
+ * Specify port to be used.
+ * \param ctl  Control object to process.
+ * \param value Port.
+ */
+void ldapctl_set_port( LdapControl* ctl, const gint value ) {
+       if( value > 0 ) {
+               ctl->port = value;
+       }
+       else {
+               ctl->port = LDAPCTL_DFL_PORT;
+       }
+}
+
+/**
+ * Specify base DN to be used.
+ * \param ctl  Control object to process.
+ * \param value Base DN.
+ */
+void ldapctl_set_base_dn( LdapControl* ctl, const gchar *value ) {
+       ctl->baseDN = mgu_replace_string( ctl->baseDN, value );
+       g_strstrip( ctl->baseDN );
+}
+
+/**
+ * Specify bind DN to be used.
+ * \param ctl  Control object to process.
+ * \param value Bind DN.
+ */
+void ldapctl_set_bind_dn( LdapControl* ctl, const gchar *value ) {
+       ctl->bindDN = mgu_replace_string( ctl->bindDN, value );
+       g_strstrip( ctl->bindDN );
+}
+
+/**
+ * Specify bind password to be used.
+ * \param ctl  Control object to process.
+ * \param value Password.
+ */
+void ldapctl_set_bind_password( LdapControl* ctl, const gchar *value ) {
+       ctl->bindPass = mgu_replace_string( ctl->bindPass, value );
+       g_strstrip( ctl->bindPass );
+}
+
+/**
+ * Specify maximum number of entries to retrieve.
+ * \param ctl  Control object to process.
+ * \param value Maximum entries.
+ */
+void ldapctl_set_max_entries( LdapControl* ctl, const gint value ) {
+       if( value > 0 ) {
+               ctl->maxEntries = value;
+       }
+       else {
+               ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
+       }
+}
+
+/**
+ * Specify timeout value for LDAP operation (in seconds).
+ * \param ctl  Control object to process.
+ * \param value Timeout.
+ */
+void ldapctl_set_timeout( LdapControl* ctl, const gint value ) {
+       if( value > 0 ) {
+               ctl->timeOut = value;
+       }
+       else {
+               ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
+       }
+}
+
+/**
+ * Specify maximum age of query (in seconds) before query is retired.
+ * \param ctl  Control object to process.
+ * \param value Maximum age.
+ */
+void ldapctl_set_max_query_age( LdapControl* ctl, const gint value ) {
+       if( value > LDAPCTL_MAX_QUERY_AGE ) {
+               ctl->maxQueryAge = LDAPCTL_MAX_QUERY_AGE;
+       }
+       else if( value < 1 ) {
+               ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
+       }
+       else {
+               ctl->maxQueryAge = value;
+       }
+}
+
+/**
+ * Specify search criteria list to be used.
+ * \param ctl   Control data object.
+ * \param value Linked list of LDAP attribute names to use for search.
+ */
+void ldapctl_set_criteria_list( LdapControl* ctl, GList *value ) {
+       g_return_if_fail( ctl != NULL );
+       mgu_free_dlist( ctl->listCriteria );
+       ctl->listCriteria = value;
+}
+
+/**
+ * Return search criteria list.
+ * \param  ctl  Control data object.
+ * \return Linked list of character strings containing LDAP attribute names to
+ *         use for a search. This should not be modified directly. Use the
+ *         <code>ldapctl_set_criteria_list()</code>,
+ *         <code>ldapctl_criteria_list_clear()</code> and
+ *         <code>ldapctl_criteria_list_add()</code> functions for this purpose.
+ */
+GList *ldapctl_get_criteria_list( const LdapControl* ctl ) {
+       g_return_val_if_fail( ctl != NULL, NULL );
+       return ctl->listCriteria;
+}
+
+/**
+ * Clear list of LDAP search attributes.
+ * \param  ctl  Control data object.
+ */
+void ldapctl_criteria_list_clear( LdapControl *ctl ) {
+       g_return_if_fail( ctl != NULL );
+       mgu_free_dlist( ctl->listCriteria );
+       ctl->listCriteria = NULL;
+}
+
+/**
+ * Add LDAP attribute to criteria list.
+ * \param ctl  Control object to process.
+ * \param attr Attribute name to append. If not NULL and unique, a copy will
+ *             be appended to the list.
+ */
+void ldapctl_criteria_list_add( LdapControl *ctl, gchar *attr ) {
+       g_return_if_fail( ctl != NULL );
+       if( attr != NULL ) {
+               if( mgu_list_test_unq_nc( ctl->listCriteria, attr ) ) {
+                       ctl->listCriteria = g_list_append(
+                               ctl->listCriteria, g_strdup( attr ) );
+               }
+       }
+}
+
+/**
+ * Build criteria list using default attributes.
+ * \param ctl  Control object to process.
+ */
+void ldapctl_default_attributes( LdapControl *ctl ) {
+       g_return_if_fail( ctl != NULL );
+
+       ldapctl_criteria_list_clear( ctl );
+       ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_COMMONNAME );
+       ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_GIVENNAME );
+       ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_SURNAME );
+       ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_EMAIL );
+}
+
+/**
+ * Clear LDAP server member variables.
+ * \param ctl Control object to clear.
+ */
+void ldapctl_clear( LdapControl *ctl ) {
+       g_return_if_fail( ctl != NULL );
+
+       /* Free internal stuff */
+       g_free( ctl->hostName );
+       g_free( ctl->baseDN );
+       g_free( ctl->bindDN );
+       g_free( ctl->bindPass );
+       g_free( ctl->attribEMail );
+       g_free( ctl->attribCName );
+       g_free( ctl->attribFName );
+       g_free( ctl->attribLName );
+
+       ldapctl_criteria_list_clear( ctl );
+
+       /* Clear pointers */
+       ctl->hostName = NULL;
+       ctl->port = 0;
+       ctl->baseDN = NULL;
+       ctl->bindDN = NULL;
+       ctl->bindPass = NULL;
+       ctl->attribEMail = NULL;
+       ctl->attribCName = NULL;
+       ctl->attribFName = NULL;
+       ctl->attribLName = NULL;
+       ctl->maxEntries = 0;
+       ctl->timeOut = 0;
+       ctl->maxQueryAge = 0;
+}
+
+/**
+ * Free up LDAP server interface object by releasing internal memory.
+ * \param ctl Control object to free.
+ */
+void ldapctl_free( LdapControl *ctl ) {
+       g_return_if_fail( ctl != NULL );
+
+       /* Free internal stuff */
+       ldapctl_clear( ctl );
+
+       /* Free the mutex */
+       pthread_mutex_destroy( ctl->mutexCtl );
+       g_free( ctl->mutexCtl );
+       ctl->mutexCtl = NULL;
+
+       /* Now release LDAP control object */
+       g_free( ctl );
+}
+
+/**
+ * Setup default (empty) values for specified object.
+ * \param ctl  Control object to process.
+ */
+void ldapctl_default_values( LdapControl *ctl ) {
+       g_return_if_fail( ctl != NULL );
+
+       /* Clear our destination */
+       ldapctl_clear( ctl );
+
+       /* Copy strings */
+       ctl->hostName = g_strdup( "" );
+       ctl->baseDN = g_strdup( "" );
+       ctl->bindDN = g_strdup( "" );
+       ctl->bindPass = g_strdup( "" );
+       ctl->port = LDAPCTL_DFL_PORT;
+       ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
+       ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
+       ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
+
+       ldapctl_default_attributes( ctl );
+}
+
+/**
+ * Display object to specified stream.
+ * \param ctl    Control object to process.
+ * \param stream Output stream.
+ */
+void ldapctl_print( const LdapControl *ctl, FILE *stream ) {
+       g_return_if_fail( ctl != NULL );
+
+       pthread_mutex_lock( ctl->mutexCtl );
+       fprintf( stream, "LdapControl:\n" );
+       fprintf( stream, "host name: '%s'\n", ctl->hostName );
+       fprintf( stream, "     port: %d\n",   ctl->port );
+       fprintf( stream, "  base dn: '%s'\n", ctl->baseDN );
+       fprintf( stream, "  bind dn: '%s'\n", ctl->bindDN );
+       fprintf( stream, "bind pass: '%s'\n", ctl->bindPass );
+       fprintf( stream, "attr mail: '%s'\n", ctl->attribEMail );
+       fprintf( stream, "attr comn: '%s'\n", ctl->attribCName );
+       fprintf( stream, "attr frst: '%s'\n", ctl->attribFName );
+       fprintf( stream, "attr last: '%s'\n", ctl->attribLName );
+       fprintf( stream, "max entry: %d\n",   ctl->maxEntries );
+       fprintf( stream, "  timeout: %d\n",   ctl->timeOut );
+       fprintf( stream, "  max age: %d\n",   ctl->maxQueryAge );
+       fprintf( stream, "crit list:\n" );
+       if( ctl->listCriteria ) {
+               mgu_print_dlist( ctl->listCriteria, stream );
+       }
+       else {
+               fprintf( stream, "\t!!!none!!!\n" );
+       }
+       pthread_mutex_unlock( ctl->mutexCtl );
+}
+
+/**
+ * Copy member variables to specified object. Mutex lock object is
+ * not copied.
+ * \param ctlFrom Object to copy from.
+ * \param ctlTo   Destination object.
+ */
+void ldapctl_copy( const LdapControl *ctlFrom, LdapControl *ctlTo ) {
+       GList *node;
+
+       g_return_if_fail( ctlFrom != NULL );
+       g_return_if_fail( ctlTo != NULL );
+
+       /* Lock both objects */
+       pthread_mutex_lock( ctlFrom->mutexCtl );
+       pthread_mutex_lock( ctlTo->mutexCtl );
+
+       /* Clear our destination */
+       ldapctl_clear( ctlTo );
+
+       /* Copy strings */
+       ctlTo->hostName = g_strdup( ctlFrom->hostName );
+       ctlTo->baseDN = g_strdup( ctlFrom->baseDN );
+       ctlTo->bindDN = g_strdup( ctlFrom->bindDN );
+       ctlTo->bindPass = g_strdup( ctlFrom->bindPass );
+       ctlTo->attribEMail = g_strdup( ctlFrom->attribEMail );
+       ctlTo->attribCName = g_strdup( ctlFrom->attribCName );
+       ctlTo->attribFName = g_strdup( ctlFrom->attribFName );
+       ctlTo->attribLName = g_strdup( ctlFrom->attribLName );
+
+       /* Copy search criteria */
+       node = ctlFrom->listCriteria;
+       while( node ) {
+               ctlTo->listCriteria = g_list_append(
+                       ctlTo->listCriteria, g_strdup( node->data ) );
+               node = g_list_next( node );
+       }
+
+       /* Copy other members */
+       ctlTo->port = ctlFrom->port;
+       ctlTo->maxEntries = ctlFrom->maxEntries;
+       ctlTo->timeOut = ctlFrom->timeOut;
+       ctlTo->maxQueryAge = ctlFrom->maxQueryAge;
+
+       /* Unlock */
+       pthread_mutex_unlock( ctlTo->mutexCtl );
+       pthread_mutex_unlock( ctlFrom->mutexCtl );
+}
+
+/**
+ * Build a formatted LDAP search criteria string from criteria list.
+ * \param ctl  Control object to process.
+ * \param searchVal Value to search for.
+ * \return Formatted string. Should be g_free() when done.
+ */
+gchar *ldapctl_format_criteria( LdapControl *ctl, const gchar *searchVal ) {
+       GList *node;
+       gchar *p1, *p2, *retVal;
+
+       g_return_val_if_fail( ctl != NULL, NULL );
+
+       /* p1 contains previous formatted criteria */
+       /* p2 contains next formatted criteria */
+       retVal = p1 = p2 = NULL;
+       node = ctl->listCriteria;
+       while( node ) {
+               gchar *attr, *tmp;
+
+               attr = node->data;
+               node = g_list_next( node );
+
+               /* Switch pointers */
+               tmp = p1; p1 = p2; p2 = tmp;
+
+               if( p1 ) {
+                       /* Subsequent time through */
+                       gchar *crit;
+
+                       /* Format query criteria */
+                       crit = g_strdup_printf( "(%s=%s*)", attr, searchVal );
+
+                       /* Append to existing criteria */                       
+                       g_free( p2 );
+                       p2 = g_strdup_printf( "(|%s%s)", p1, crit );
+
+                       g_free( crit );
+               }
+               else {
+                       /* First time through - Format query criteria */
+                       p2 = g_strdup_printf( "(%s=%s*)", attr, searchVal );
+               }
+       }
+
+       if( p2 == NULL ) {
+               /* Nothing processed - format a default attribute */
+               retVal = g_strdup_printf( "(%s=*)", LDAPCTL_ATTR_EMAIL );
+       }
+       else {
+               /* We have something - free up previous result */
+               retVal = p2;
+               g_free( p1 );
+       }
+       return retVal;
+}
+
+/**
+ * Return array of pointers to attributes for LDAP query.
+ * \param  ctl  Control object to process.
+ * \return NULL terminated list.
+ */
+char **ldapctl_attribute_array( LdapControl *ctl ) {
+       char **ptrArray;
+       GList *node;
+       gint cnt, i;
+       g_return_val_if_fail( ctl != NULL, NULL );
+
+       cnt = g_list_length( ctl->listCriteria );
+       ptrArray = g_new0( char *, 1 + cnt );
+       i = 0;
+       node = ctl->listCriteria;
+       while( node ) {
+               ptrArray[ i++ ] = node->data;
+               node = g_list_next( node );
+       }
+       ptrArray[ i ] = NULL;
+       return ptrArray;
+}
+
+/**
+ * Free array of pointers allocated by ldapctl_criteria_array().
+ * param ptrArray Array to clear.
+ */
+void ldapctl_free_attribute_array( char **ptrArray ) {
+       gint i;
+
+       /* Clear array to NULL's */
+       for( i = 0; ptrArray[i] != NULL; i++ ) {
+               ptrArray[i] = NULL;
+       }
+       g_free( ptrArray );
+}      
+
+/**
+ * Parse LDAP search string, building list of LDAP criteria attributes. This
+ * may be used to convert an old style Sylpheed LDAP search criteria to the
+ * new format. The old style uses a standard LDAP search string, for example:
+ * <pre>
+ *    (&(mail=*)(cn=%s*))
+ * </pre>
+ * This function extracts the two LDAP attributes <code>mail</code> and
+ * <code>cn</code>, adding each to a list.
+ *
+ * \param ctl Control object to process.
+ * \param criteria LDAP search criteria string.
+ */
+void ldapctl_parse_ldap_search( LdapControl *ctl, gchar *criteria ) {
+       gchar *ptr;
+       gchar *pFrom;
+       gchar *attrib;
+       gint iLen;
+
+       g_return_if_fail( ctl != NULL );
+
+       ldapctl_criteria_list_clear( ctl );
+       if( criteria == NULL ) return;
+
+       pFrom = NULL;
+       ptr = criteria;
+       while( *ptr ) {
+               if( *ptr == '(' ) {
+                       pFrom = 1 + ptr;
+               }
+               if( *ptr == '=' ) {
+                       if( pFrom ) {
+                               iLen = ptr - pFrom;
+                               attrib = g_strndup( pFrom, iLen );
+                               g_strstrip( attrib );
+                               ldapctl_criteria_list_add( ctl, attrib );
+                               g_free( attrib );
+                       }
+                       pFrom = NULL;
+               }
+               ptr++;
+       }
+}
+
+#endif /* USE_LDAP */
+
+/*
+ * End of Source.
+ */
+
diff --git a/src/ldapctrl.h b/src/ldapctrl.h
new file mode 100644 (file)
index 0000000..b5ffa37
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions for LDAP control data.
+ */
+
+#ifndef __LDAPCTRL_H__
+#define __LDAPCTRL_H__
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <stdio.h>
+#include <pthread.h>
+
+/*
+ * Constants.
+ */
+#define LDAPCTL_DFL_PORT        389
+#define LDAPCTL_MAX_ENTRIES     30
+#define LDAPCTL_DFL_TIMEOUT     30
+#define LDAPCTL_DFL_QUERY_AGE   600
+#define LDAPCTL_MAX_QUERY_AGE   86400
+
+#define LDAPCTL_ATTR_EMAIL      "mail"
+#define LDAPCTL_ATTR_COMMONNAME "cn"
+#define LDAPCTL_ATTR_GIVENNAME  "givenName"
+#define LDAPCTL_ATTR_SURNAME    "sn"
+
+#define LDAPCTL_DFL_ATTR_LIST   "mail, cn, givenName, sn"
+
+/*
+ * Data structures.
+ */
+typedef struct _LdapControl LdapControl;
+struct _LdapControl {
+       gchar     *hostName;
+       gint      port;
+       gchar     *baseDN;
+       gchar     *bindDN;
+       gchar     *bindPass;
+       gint      maxEntries;
+       gint      timeOut;
+       gint      maxQueryAge;
+       gchar     *attribEMail;
+       gchar     *attribCName;
+       gchar     *attribFName;
+       gchar     *attribLName;
+       GList     *listCriteria;
+       pthread_mutex_t *mutexCtl;
+};
+
+/* Function prototypes */
+LdapControl *ldapctl_create    ( void );
+void ldapctl_set_host          ( LdapControl* ctl, const gchar *value );
+void ldapctl_set_port          ( LdapControl* ctl, const gint value );
+void ldapctl_set_base_dn       ( LdapControl* ctl, const gchar *value );
+void ldapctl_set_bind_dn       ( LdapControl* ctl, const gchar *value );
+void ldapctl_set_bind_password ( LdapControl* ctl, const gchar *value );
+void ldapctl_set_max_entries   ( LdapControl* ctl, const gint value );
+void ldapctl_set_timeout       ( LdapControl* ctl, const gint value );
+void ldapctl_set_max_query_age ( LdapControl* ctl, const gint value );
+void ldapctl_set_criteria_list ( LdapControl* ctl, GList *value );
+GList *ldapctl_get_criteria_list( const LdapControl* ctl );
+void ldapctl_criteria_list_clear( LdapControl *ctl );
+void ldapctl_criteria_list_add ( LdapControl *ctl, gchar *attr );
+void ldapctl_default_attributes        ( LdapControl *ctl );
+void ldapctl_clear             ( LdapControl *ctl );
+void ldapctl_free              ( LdapControl *ctl );
+void ldapctl_default_values    ( LdapControl *ctl );
+void ldapctl_print             ( const LdapControl *ctl, FILE *stream );
+void ldapctl_copy              ( const LdapControl *ctlFrom,
+                                 LdapControl *ctlTo );
+gchar *ldapctl_format_criteria ( LdapControl *ctl, const gchar *searchVal );
+char **ldapctl_attribute_array ( LdapControl *ctl );
+void ldapctl_free_attribute_array( char **ptrArray );
+void ldapctl_parse_ldap_search ( LdapControl *ctl, gchar *criteria );
+
+#endif /* USE_LDAP */
+
+#endif /* __LDAPCTRL_H__ */
+
diff --git a/src/ldapquery.c b/src/ldapquery.c
new file mode 100644 (file)
index 0000000..53bdb17
--- /dev/null
@@ -0,0 +1,1078 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions necessary to define and perform LDAP queries.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <sys/time.h>
+#include <string.h>
+#include <ldap.h>
+#include <lber.h>
+
+#include "ldapquery.h"
+#include "ldapctrl.h"
+#include "mgutils.h"
+
+#include "addritem.h"
+#include "addrcache.h"
+
+/*
+ * Key for thread specific data.
+ */
+static pthread_key_t _queryThreadKey_;
+static gboolean _queryThreadInit_ = FALSE;
+
+/**
+ * Create new LDAP query object.
+ * \return Initialized query object.
+ */
+LdapQuery *ldapqry_create( void ) {
+       LdapQuery *qry;
+
+       qry = g_new0( LdapQuery, 1 );
+       qry->control = NULL;
+       qry->retVal = LDAPRC_SUCCESS;
+       qry->queryType = LDAPQUERY_NONE;
+       qry->queryName = NULL;
+       qry->searchValue = NULL;
+       qry->queryID = 0;
+       qry->entriesRead = 0;
+       qry->elapsedTime = 0;
+       qry->stopFlag = FALSE;
+       qry->busyFlag = FALSE;
+       qry->agedFlag = FALSE;
+       qry->completed = FALSE;
+       qry->thread = NULL;
+       qry->callBackStart = NULL;
+       qry->callBackEntry = NULL;
+       qry->callBackEnd = NULL;
+       qry->folder = NULL;
+       qry->server = NULL;
+
+       /* Mutex to protect stop and busy flags */
+       qry->mutexStop = g_malloc0( sizeof( pthread_mutex_t ) );
+       pthread_mutex_init( qry->mutexStop, NULL );
+       qry->mutexBusy = g_malloc0( sizeof( pthread_mutex_t ) );
+       pthread_mutex_init( qry->mutexBusy, NULL );
+
+       /* Mutex to protect critical section */
+       qry->mutexEntry = g_malloc0( sizeof( pthread_mutex_t ) );
+       pthread_mutex_init( qry->mutexEntry, NULL );
+
+       return qry;
+}
+
+/**
+ * Specify the reference to control data that will be used for the query. The calling
+ * module should be responsible for creating and destroying this control object.
+ * \param qry Query object.
+ * \param ctl Control object.
+ */
+void ldapqry_set_control( LdapQuery *qry, LdapControl *ctl ) {
+       g_return_if_fail( qry != NULL );
+       qry->control = ctl;
+}
+
+/**
+ * Specify query name to be used.
+ * \param qry   Query object.
+ * \param value Name.
+ */
+void ldapqry_set_name( LdapQuery* qry, const gchar *value ) {
+       qry->queryName = mgu_replace_string( qry->queryName, value );
+       g_strstrip( qry->queryName );
+}
+
+/**
+ * Specify search value to be used.
+ * \param qry Query object.
+ * \param value 
+ */
+void ldapqry_set_search_value( LdapQuery *qry, const gchar *value ) {
+       qry->searchValue = mgu_replace_string( qry->searchValue, value );
+       g_strstrip( qry->searchValue );
+}
+
+/**
+ * Specify error/status.
+ * \param qry   Query object.
+ * \param value Status.
+ */
+void ldapqry_set_error_status( LdapQuery* qry, const gint value ) {
+       qry->retVal = value;
+}
+
+/**
+ * Specify query type.
+ * \param qry Query object.
+ * \param value Query type, either:
+ * <ul>
+ * <li><code>LDAPQUERY_NONE</code></li>
+ * <li><code>LDAPQUERY_STATIC</code></li>
+ * <li><code>LDAPQUERY_DYNAMIC</code></li>
+ * </ul>
+ */
+void ldapqry_set_query_type( LdapQuery* qry, const gint value ) {
+       qry->queryType = value;
+}
+
+/**
+ * Specify query ID.
+ * \param qry Query object.
+ * \param value ID for the query.
+ */
+void ldapqry_set_query_id( LdapQuery* qry, const gint value ) {
+       qry->queryID = value;
+}
+
+/**
+ * Specify maximum number of LDAP entries to retrieve.
+ * \param qry Query object.
+ * \param value Entries to read.
+ */
+void ldapqry_set_entries_read( LdapQuery* qry, const gint value ) {
+       if( value > 0 ) {
+               qry->entriesRead = value;
+       }
+       else {
+               qry->entriesRead = 0;
+       }
+}
+
+/**
+ * Register a callback function that will be executed when the search
+ * starts. When called, the function will be passed this query object
+ * as an argument.
+ * \param qry Query object.
+ * \param func Function.
+ */
+void ldapqry_set_callback_start( LdapQuery *qry, void *func ) {
+       qry->callBackStart = func;
+}
+
+/**
+ * Register a callback function that will be executed when each entry
+ * has been read and processed. When called, the function will be passed
+ * this query object and a GList of ItemEMail objects as arguments. An
+ * example of typical usage is shown below.
+ *
+ * <pre>
+ * ------------------------------------------------------------
+ * void myCallbackEntry( LdapQuery *qry, GList *listEMail ) {
+ *   GList *node;
+ *
+ *   node = listEMail;
+ *   while( node ) {
+ *     ItemEMail *email = node->data;
+ *     ... process email object ...
+ *     node = g_list_next( node );
+ *   }
+ *   g_list_free( listEMail );
+ * }
+ * ...
+ * ...
+ * ldapqry_set_callback_entry( qry, myCallbackEntry );
+ * ------------------------------------------------------------
+ * </pre>
+ *
+ * \param qry Query object.
+ * \param func Function.
+ */
+void ldapqry_set_callback_entry( LdapQuery *qry, void *func ) {
+       pthread_mutex_lock( qry->mutexEntry );
+       qry->callBackEntry = func;
+       pthread_mutex_unlock( qry->mutexEntry );
+}
+
+/**
+ * Register a callback function that will be executed when the search
+ * is complete. When called, the function will be passed this query
+ * object as an argument.
+ * \param qry Query object.
+ * \param func Function.
+ */
+void ldapqry_set_callback_end( LdapQuery *qry, void *func ) {
+       qry->callBackEnd = func;
+}
+
+/**
+ * Notify query to start/stop executing. This method should be called with a
+ * value if <i>TRUE</i> to terminate an existing running query.
+ *
+ * \param qry Query object.
+ * \param value Value of stop flag.
+ */
+void ldapqry_set_stop_flag( LdapQuery *qry, const gboolean value ) {
+       g_return_if_fail( qry != NULL );
+
+       pthread_mutex_lock( qry->mutexStop );
+       qry->stopFlag = value;
+       pthread_mutex_unlock( qry->mutexStop );
+}
+
+/**
+ * Test value of stop flag. This method should be used to determine whether a
+ * query has stopped running.
+ * \param qry Query object.
+ * \return Value of stop flag.
+ */
+gboolean ldapqry_get_stop_flag( LdapQuery *qry ) {
+       gboolean value;
+       g_return_if_fail( qry != NULL );
+
+       pthread_mutex_lock( qry->mutexStop );
+       value = qry->stopFlag;
+       pthread_mutex_unlock( qry->mutexStop );
+       return value;
+}
+
+/**
+ * Set busy flag.
+ * \param qry Query object.
+ * \param value Value of busy flag.
+ */
+void ldapqry_set_busy_flag( LdapQuery *qry, const gboolean value ) {
+       g_return_if_fail( qry != NULL );
+
+       pthread_mutex_lock( qry->mutexBusy );
+       qry->busyFlag = value;
+       pthread_mutex_unlock( qry->mutexBusy );
+}
+
+/**
+ * Test value of busy flag. This method will return a value of <i>FALSE</i>
+ * when a query has completed running.
+ * \param qry Query object.
+ * \return Value of busy flag.
+ */
+gboolean ldapqry_get_busy_flag( LdapQuery *qry ) {
+       gboolean value;
+       g_return_if_fail( qry != NULL );
+
+       pthread_mutex_lock( qry->mutexBusy );
+       value = qry->busyFlag;
+       pthread_mutex_unlock( qry->mutexBusy );
+       return value;
+}
+
+/**
+ * Set query aged flag.
+ * \param qry Query object.
+ * \param value Value of aged flag.
+ */
+void ldapqry_set_aged_flag( LdapQuery *qry, const gboolean value ) {
+       g_return_if_fail( qry != NULL );
+       qry->agedFlag = value;
+}
+
+/**
+ * Test value of aged flag.
+ * \param qry Query object.
+ * \return <i>TRUE</i> if query has been marked as aged (and can be retired).
+ */
+gboolean ldapqry_get_aged_flag( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+       return qry->agedFlag;
+}
+
+/**
+ * Release the thread associated with the query.
+ * \param qry Query object to process.
+ */
+void ldapqry_release_thread( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+       printf( "ldapqry_release_thread...\n" );
+       if( qry->thread != NULL ) {
+               g_free( qry->thread );
+               printf( "\t===========>done\n" );
+       }
+       qry->thread = NULL;
+}
+
+/**
+ * Release the LDAP control data associated with the query.
+ * \param qry Query object to process.
+ */
+void ldapqry_release_control( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+       if( qry->control != NULL ) {
+               ldapctl_free( qry->control );
+       }
+       qry->control = NULL;
+}
+
+/**
+ * Clear LDAP query member variables.
+ * \param qry Query object.
+ */
+void ldapqry_clear( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+
+       /* Free internal stuff */
+       g_free( qry->queryName );
+       g_free( qry->searchValue );
+
+       /* Clear pointers and value */
+       qry->queryName = NULL;
+       qry->searchValue = NULL;
+       qry->retVal = LDAPRC_SUCCESS;
+       qry->queryType = LDAPQUERY_NONE;
+       qry->queryID = 0;
+       qry->entriesRead = 0;
+       qry->elapsedTime = 0;
+       qry->stopFlag = FALSE;
+       qry->busyFlag = FALSE;
+       qry->agedFlag = FALSE;
+       qry->completed = FALSE;
+       qry->callBackStart = NULL;
+       qry->callBackEntry = NULL;
+       qry->callBackEnd = NULL;
+}
+
+/**
+ * Free up LDAP query object by releasing internal memory.
+ * \param qry Query object to process.
+ */
+void ldapqry_free( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+
+       /* Clear out internal members */
+       ldapqry_clear( qry );
+
+       /* Free the mutex */
+       pthread_mutex_destroy( qry->mutexStop );
+       pthread_mutex_destroy( qry->mutexBusy );
+       pthread_mutex_destroy( qry->mutexEntry );
+       g_free( qry->mutexStop );
+       g_free( qry->mutexBusy );
+       g_free( qry->mutexEntry );
+       qry->mutexEntry = NULL;
+       qry->mutexBusy = NULL;
+       qry->mutexStop = NULL;
+
+       /* Do not free folder - parent server object should free */     
+       qry->folder = NULL;
+
+       /* Do not free thread - thread should be terminated before freeing */
+       qry->thread = NULL;
+
+       /* Do not free LDAP control - should be destroyed before freeing */
+       qry->control = NULL;
+
+       /* Now release object */
+       g_free( qry );
+}
+
+/**
+ * Display object to specified stream.
+ * \param qry    Query object to process.
+ * \param stream Output stream.
+ */
+void ldapqry_print( const LdapQuery *qry, FILE *stream ) {
+       g_return_if_fail( qry != NULL );
+
+       fprintf( stream, "LdapQuery:\n" );
+       fprintf( stream, "  control?: %s\n",   qry->control ? "yes" : "no" );
+       fprintf( stream, "err/status: %d\n",   qry->retVal );
+       fprintf( stream, "query type: %d\n",   qry->queryType );
+       fprintf( stream, "query name: '%s'\n", qry->queryName );
+       fprintf( stream, "search val: '%s'\n", qry->searchValue );
+       fprintf( stream, "   queryID: %d\n",   qry->queryID );
+       fprintf( stream, "   entries: %d\n",   qry->entriesRead );
+       fprintf( stream, "   elapsed: %d\n",   qry->elapsedTime );
+       fprintf( stream, " stop flag: %s\n",   qry->stopFlag  ? "yes" : "no" );
+       fprintf( stream, " busy flag: %s\n",   qry->busyFlag  ? "yes" : "no" );
+       fprintf( stream, " aged flag: %s\n",   qry->agedFlag  ? "yes" : "no" );
+       fprintf( stream, " completed: %s\n",   qry->completed ? "yes" : "no" );
+}
+
+/**
+ * Free linked lists of character strings.
+ * \param listName  List of common names.
+ * \param listAddr  List of addresses.
+ * \param listFirst List of first names.
+ * \param listLast  List of last names.
+ */
+static void ldapqry_free_lists(
+               GSList *listName, GSList *listAddr, GSList *listFirst,
+               GSList *listLast )
+{
+       mgu_free_list( listName );
+       mgu_free_list( listAddr );
+       mgu_free_list( listFirst );
+       mgu_free_list( listLast );
+}
+
+/**
+ * Add all LDAP attribute values to a list.
+ * \param ld LDAP handle.
+ * \param entry LDAP entry to process.
+ * \param attr  LDAP attribute.
+ * \return List of values.
+ */
+static GSList *ldapqry_add_list_values(
+               LDAP *ld, LDAPMessage *entry, char *attr )
+{
+       GSList *list = NULL;
+       gint i;
+       gchar **vals;
+
+       if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
+               for( i = 0; vals[i] != NULL; i++ ) {
+                       /* printf( "lv\t%s: %s\n", attr, vals[i] ); */
+                       list = g_slist_append( list, g_strdup( vals[i] ) );
+               }
+       }
+       ldap_value_free( vals );
+       return list;
+}
+
+/**
+ * Add a single attribute value to a list.
+ * \param  ld    LDAP handle.
+ * \param  entry LDAP entry to process.
+ * \param  attr  LDAP attribute name to process.
+ * \return List of values; only one value will be present.
+ */
+static GSList *ldapqry_add_single_value( LDAP *ld, LDAPMessage *entry, char *attr ) {
+       GSList *list = NULL;
+       gchar **vals;
+
+       if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
+               if( vals[0] != NULL ) {
+                       /* printf( "sv\t%s: %s\n", attr, vals[0] ); */
+                       list = g_slist_append( list, g_strdup( vals[0] ) );
+               }
+       }
+       ldap_value_free( vals );
+       return list;
+}
+
+/**
+ * Build an address list entry and append to list of address items. Name is formatted
+ * as "<first-name> <last-name>".
+ *
+ * \param  cache     Address cache to load.
+ * \param  qry Query object to process.
+ * \param  dn        DN for entry found on server.
+ * \param  listName  List of common names for entry; see notes below.
+ * \param  listAddr  List of EMail addresses for entry.
+ * \param  listFirst List of first names for entry.
+ * \param  listLast  List of last names for entry.
+ *
+ * \return List of ItemEMail objects.
+ *
+ * Notes:
+ * 1) Each LDAP server entry may have multiple LDAP attributes with the same
+ *    name. For example, a single entry for a person may have more than one
+ *    common name, email address, etc.
+*
+ * 2) The DN for the entry is unique for the server.
+ */
+static GList *ldapqry_build_items_fl(
+               AddressCache *cache, LdapQuery *qry, gchar *dn,
+               GSList *listName, GSList *listAddr, GSList *listFirst,
+               GSList *listLast )
+{
+       GSList *nodeAddress;
+       gchar *firstName = NULL, *lastName = NULL, *fullName = NULL;
+       gboolean allocated;
+       ItemPerson *person;
+       ItemEMail *email;
+       ItemFolder *folder;
+       GList *listReturn;
+
+       listReturn = NULL;
+
+       /* Find longest first name in list */
+       firstName = mgu_slist_longest_entry( listFirst );
+
+       /* Format last name */
+       if( listLast ) {
+               lastName = listLast->data;
+       }
+
+       /* Find longest common name */
+       allocated = FALSE;
+       fullName = mgu_slist_longest_entry( listName );
+       if( fullName == NULL ) {
+               /* Format a full name from first and last names */
+               if( firstName ) {
+                       if( lastName ) {
+                               fullName = g_strdup_printf( "%s %s", firstName, lastName );
+                       }
+                       else {
+                               fullName = g_strdup_printf( "%s", firstName );
+                       }
+               }
+               else {
+                       if( lastName ) {
+                               fullName = g_strdup_printf( "%s", lastName );
+                       }
+               }
+               if( fullName ) {
+                       g_strchug( fullName ); g_strchomp( fullName );
+                       allocated = TRUE;
+               }
+       }
+
+       if( listAddr ) {
+               /* Create new folder for results */
+               if( qry->folder == NULL ) {
+                       folder = addritem_create_item_folder();
+                       addritem_folder_set_name( folder, qry->queryName );
+                       addritem_folder_set_remarks( folder, "" );
+                       addrcache_id_folder( cache, folder );
+                       addrcache_add_folder( cache, folder );
+                       qry->folder = folder;
+
+                       /* Specify folder type and back reference */                    
+                       folder->folderType = ADDRFOLDER_LDAP_QUERY;
+                       folder->folderData = ( gpointer ) qry;
+               }
+
+               /* Add person into folder */            
+               person = addritem_create_item_person();
+               addritem_person_set_common_name( person, fullName );
+               addritem_person_set_first_name( person, firstName );
+               addritem_person_set_last_name( person, lastName );
+               addrcache_id_person( cache, person );
+               addritem_person_set_external_id( person, dn );
+               addrcache_folder_add_person( cache, qry->folder, person );
+
+               qry->entriesRead++;
+       }
+
+       /* Add each address item */
+       nodeAddress = listAddr;
+       while( nodeAddress ) {
+               email = addritem_create_item_email();
+               addritem_email_set_address( email, nodeAddress->data );
+               addrcache_id_email( cache, email );
+               addrcache_person_add_email( cache, person, email );
+               addritem_person_add_email( person, email );
+               listReturn = g_list_append( listReturn, email );
+               nodeAddress = g_slist_next( nodeAddress );
+       }
+
+       /* Free any allocated memory */
+       if( allocated ) {
+               g_free( fullName );
+       }
+       fullName = firstName = lastName = NULL;
+
+       return listReturn;
+}
+
+/**
+ * Process a single search entry.
+ * \param  cache Address cache to load.
+ * \param  qry   Query object to process.
+ * \param  ld    LDAP handle.
+ * \param  e     LDAP message.
+ * \return List of EMail objects found.
+ */
+static GList *ldapqry_process_single_entry(
+               AddressCache *cache, LdapQuery *qry, LDAP *ld, LDAPMessage *e )
+{
+       char *dnEntry;
+       char *attribute;
+       LdapControl *ctl;
+       BerElement *ber;
+       GSList *listName = NULL, *listAddress = NULL;
+       GSList *listFirst = NULL, *listLast = NULL;
+       GList *listReturn;
+
+       listReturn = NULL;
+       ctl = qry->control;
+       dnEntry = ldap_get_dn( ld, e );
+       /* printf( "DN: %s\n", dnEntry ); */
+
+       /* Process all attributes */
+       for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
+               attribute = ldap_next_attribute( ld, e, ber ) ) {
+
+               if( strcasecmp( attribute, ctl->attribEMail ) == 0 ) {
+                       listAddress = ldapqry_add_list_values( ld, e, attribute );
+               }
+               else if( strcasecmp( attribute, ctl->attribCName ) == 0 ) {
+                       listName = ldapqry_add_list_values( ld, e, attribute );
+               }
+               else if( strcasecmp( attribute, ctl->attribFName ) == 0 ) {
+                       listFirst = ldapqry_add_list_values( ld, e, attribute );
+               }
+               else if( strcasecmp( attribute, ctl->attribLName ) == 0 ) {
+                       listLast = ldapqry_add_single_value( ld, e, attribute );
+               }
+
+               /* Free memory used to store attribute */
+               ldap_memfree( attribute );
+       }
+
+       /* Format and add items to cache */
+       listReturn = ldapqry_build_items_fl(
+               cache, qry, dnEntry, listName, listAddress, listFirst, listLast );
+
+       /* Free up */
+       ldapqry_free_lists( listName, listAddress, listFirst, listLast );
+       listName = listAddress = listFirst = listLast = NULL;
+
+       if( ber != NULL ) {
+               ber_free( ber, 0 );
+       }
+       g_free( dnEntry );
+
+       return listReturn;
+}
+
+/**
+ * Check parameters that are required for a search. This should
+ * be called before performing a search.
+ * \param  qry Query object to process.
+ * \return <i>TRUE</i> if search criteria appear OK.
+ */
+gboolean ldapqry_check_search( LdapQuery *qry ) {
+       LdapControl *ctl;
+       qry->retVal = LDAPRC_CRITERIA;
+
+       /* Test for control data */
+       ctl = qry->control;
+       if( ctl == NULL ) {
+               return FALSE;
+       }
+
+       /* Test for search value */
+       if( qry->searchValue == NULL ) {
+               return FALSE;
+       }
+       if( strlen( qry->searchValue ) < 1 ) {
+               return FALSE;
+       }
+
+       qry->retVal = LDAPRC_SUCCESS;
+       return TRUE;
+}
+
+/**
+ * Touch the query. This nudges the touch time with the current time.
+ * \param qry Query object to process.
+ */
+void ldapqry_touch( LdapQuery *qry ) {
+       qry->touchTime = time( NULL );
+       qry->agedFlag = FALSE;
+}
+
+/**
+ * Perform the LDAP search, reading LDAP entries into cache.
+ * Note that one LDAP entry can have multiple values for many of its
+ * attributes. If these attributes are E-Mail addresses; these are
+ * broken out into separate address items. For any other attribute,
+ * only the first occurrence is read.
+ * 
+ * \param  qry Query object to process.
+ * \return Error/status code.
+ */
+static gint ldapqry_perform_search( LdapQuery *qry ) {
+       LdapControl *ctl;
+       LDAP *ld;
+       LDAPMessage *result, *e;
+       char **attribs;
+       gchar *criteria;
+       gboolean entriesFound;
+       gboolean first;
+       struct timeval timeout;
+       gint rc;
+       time_t tstart, tend;
+       AddressCache *cache;
+       GList *listEMail;
+
+       /* Initialize some variables */
+       ctl = qry->control;
+       cache = qry->server->addressCache;
+       timeout.tv_sec = ctl->timeOut;
+       timeout.tv_usec = 0L;
+       entriesFound = FALSE;
+       qry->elapsedTime = -1;
+       qry->retVal = LDAPRC_SUCCESS;
+
+       /* Check search criteria */     
+       if( ! ldapqry_check_search( qry ) ) {
+               return qry->retVal;
+       }
+
+       /* Initialize connection */
+       ldapqry_touch( qry );
+       tstart = qry->touchTime;
+       tend = tstart - 1;
+       if( ( ld = ldap_init( ctl->hostName, ctl->port ) ) == NULL ) {
+               qry->retVal = LDAPRC_INIT;
+               return qry->retVal;
+       }
+       if( ldapqry_get_stop_flag( qry ) ) {
+               qry->retVal = LDAPRC_SUCCESS;
+               return qry->retVal;
+       }
+       ldapqry_touch( qry );
+
+       /*
+       printf( "connected to LDAP host %s on port %d\n", ctl->hostName, ctl->port );
+       */
+
+       /* Bind to the server, if required */
+       if( ctl->bindDN ) {
+               if( * ctl->bindDN != '\0' ) {
+                       /* printf( "binding...\n" ); */
+                       rc = ldap_simple_bind_s( ld, ctl->bindDN, ctl->bindPass );
+                       /* printf( "rc=%d\n", rc ); */
+                       if( rc != LDAP_SUCCESS ) {
+                               /*
+                               printf( "LDAP Error: ldap_simple_bind_s: %s\n",
+                                       ldap_err2string( rc ) );
+                               */
+                               ldap_unbind( ld );
+                               qry->retVal = LDAPRC_BIND;
+                               return qry->retVal;
+                       }
+               }
+       }
+       if( ldapqry_get_stop_flag( qry ) ) {
+               ldap_unbind( ld );
+               qry->retVal = LDAPRC_SUCCESS;
+               return qry->retVal;
+       }
+       ldapqry_touch( qry );
+
+       /* Define all attributes we are interested in. */
+       attribs = ldapctl_attribute_array( ctl );
+
+       /* Create LDAP search string */
+       criteria = ldapctl_format_criteria( ctl, qry->searchValue );
+       /* printf( "Search criteria ::%s::\n", criteria ); */
+
+       /*
+        * Execute the search - this step may take some time to complete
+        * depending on network traffic and server response time.
+        */
+       rc = ldap_search_ext_s( ld, ctl->baseDN, LDAP_SCOPE_SUBTREE, criteria,
+               attribs, 0, NULL, NULL, &timeout, 0, &result );
+       ldapctl_free_attribute_array( attribs );
+       g_free( criteria );
+       criteria = NULL;
+       if( rc == LDAP_TIMEOUT ) {
+               ldap_unbind( ld );
+               qry->retVal = LDAPRC_TIMEOUT;
+               return qry->retVal;
+       }
+       if( rc != LDAP_SUCCESS ) {
+               /*
+               printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) );
+               */
+               ldap_unbind( ld );
+               qry->retVal = LDAPRC_SEARCH;
+               return qry->retVal;
+       }
+       if( ldapqry_get_stop_flag( qry ) ) {
+               qry->retVal = LDAPRC_SUCCESS;
+               return qry->retVal;
+       }
+       ldapqry_touch( qry );
+
+       /*
+       printf( "Total results are: %d\n", ldap_count_entries( ld, result ) );
+       */
+
+       if( ldapqry_get_stop_flag( qry ) ) {
+               qry->retVal = LDAPRC_SUCCESS;
+               return qry->retVal;
+       }
+
+       /* Process results */
+       first = TRUE;
+       while( TRUE ) {
+               ldapqry_touch( qry );
+               if( qry->entriesRead >= ctl->maxEntries ) break;                
+
+               /* Test for stop */             
+               if( ldapqry_get_stop_flag( qry ) ) {
+                       break;
+               }
+
+               /* Retrieve entry */            
+               if( first ) {
+                       first = FALSE;
+                       e = ldap_first_entry( ld, result );
+               }
+               else {
+                       e = ldap_next_entry( ld, e );
+               }
+               if( e == NULL ) break;
+
+               entriesFound = TRUE;
+
+               /* Setup a critical section here */
+               pthread_mutex_lock( qry->mutexEntry );
+
+               /* Process entry */
+               listEMail = ldapqry_process_single_entry( cache, qry, ld, e );
+
+               /* Process callback */
+               if( qry->callBackEntry ) {
+                       qry->callBackEntry( qry, listEMail );
+               }
+               else {
+                       g_list_free( listEMail );
+               }
+
+               pthread_mutex_unlock( qry->mutexEntry );
+       }
+
+       /* Free up and disconnect */
+       ldap_msgfree( result );
+       ldap_unbind( ld );
+       ldapqry_touch( qry );
+       tend = qry->touchTime;
+       qry->elapsedTime = tend - tstart;
+
+       if( entriesFound ) {
+               qry->retVal = LDAPRC_SUCCESS;
+       }
+       else {
+               qry->retVal = LDAPRC_NOENTRIES;
+       }
+
+       return qry->retVal;
+}
+
+/**
+ * Wrapper around search.
+ * \param  qry Query object to process.
+ * \return Error/status code.
+ */
+gint ldapqry_search( LdapQuery *qry ) {
+       gint retVal;
+
+       g_return_val_if_fail( qry != NULL, -1 );
+       g_return_val_if_fail( qry->control != NULL, -1 );
+
+       ldapqry_touch( qry );
+       qry->completed = FALSE;
+
+       /* Process callback */  
+       if( qry->callBackStart ) {
+               qry->callBackStart( qry );
+       }
+
+       /* Setup pointer to thread specific area */
+       pthread_setspecific( _queryThreadKey_, qry );
+
+       pthread_detach( pthread_self() );
+       
+       /* Now perform the search */
+       qry->entriesRead = 0;
+       qry->retVal = LDAPRC_SUCCESS;
+       ldapqry_set_busy_flag( qry, TRUE );
+       ldapqry_set_stop_flag( qry, FALSE );
+       retVal = ldapqry_perform_search( qry );
+       if( retVal == LDAPRC_SUCCESS ) {
+               qry->server->addressCache->dataRead = TRUE;
+               qry->server->addressCache->accessFlag = FALSE;
+               if( ldapqry_get_stop_flag( qry ) ) {
+                       /*
+                       printf( "Search was terminated prematurely\n" );
+                       */
+               }
+               else {
+                       ldapqry_touch( qry );
+                       qry->completed = TRUE;
+                       /*
+                       printf( "Search ran to completion\n" );
+                       */
+               }
+       }
+       ldapqry_set_stop_flag( qry, TRUE );
+       ldapqry_set_busy_flag( qry, FALSE );
+
+       /* Process callback */  
+       if( qry->callBackEnd ) {
+               qry->callBackEnd( qry );
+       }
+
+       return qry->retVal;
+}
+
+/**
+ * Read data into list using a background thread. Callback function will be
+ * notified when search is complete.
+ * \param  qry Query object to process.
+ * \return Error/status code.
+ */
+gint ldapqry_read_data_th( LdapQuery *qry ) {
+       g_return_val_if_fail( qry != NULL, -1 );
+       g_return_val_if_fail( qry->control != NULL, -1 );
+
+       ldapqry_set_stop_flag( qry, FALSE );
+       ldapqry_touch( qry );
+       if( ldapqry_check_search( qry ) ) {
+               if( qry->retVal == LDAPRC_SUCCESS ) {
+                       /*
+                       printf( "Starting LDAP search thread\n");
+                       */
+                       ldapqry_set_busy_flag( qry, TRUE );
+                       qry->thread = g_malloc0( sizeof( pthread_t ) );
+
+                       /* Setup thread */                      
+                       pthread_create( qry->thread, NULL,
+                               (void *) ldapqry_search, (void *) qry );
+               }
+       }
+       return qry->retVal;
+}
+
+/**
+ * Join the thread associated with the query. This should probably be removed
+ * to prevent joining threads.
+ * \param qry Query object to process.
+ */
+void ldapqry_join_thread( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+
+       /* Wait for thread */
+       /* printf( "ldapqry_join_thread::Joining thread...\n" ); */
+       pthread_join( * qry->thread, NULL );
+       /* printf( "ldapqry_join_thread::Thread terminated\n" ); */
+}
+
+/**
+ * Cleanup LDAP thread data. This function will be called when each thread
+ * exits.
+ * \param ptr Pointer to object being destroyed (a query object in this case).
+ */
+static void ldapqry_destroyer( void * ptr ) {
+       LdapQuery *qry;
+
+       qry = ( LdapQuery * ) ptr;
+       /*
+       printf( "ldapqry_destroyer::%d::%s\n", (int) pthread_self(), qry->queryName );
+       */
+
+       /* Perform any destruction here */
+       if( qry->control != NULL ) {
+               ldapctl_free( qry->control );
+       }
+       qry->control = NULL;
+
+       if( qry->thread ) {
+               g_free( qry->thread );
+       }
+       qry->thread = NULL;
+       ldapqry_set_busy_flag( qry, FALSE );
+       /*
+       printf( "...destroy exiting\n" );
+       */
+}
+
+/**
+ * Cancel thread associated with query.
+ * \param qry Query object to process.
+ */
+void ldapqry_cancel( LdapQuery *qry ) {
+       g_return_if_fail( qry != NULL );
+
+       /*
+       printf( "cancelling::%d::%s\n", (int) pthread_self(), qry->queryName );
+       */
+       if( ldapqry_get_busy_flag( qry ) ) {
+               if( qry->thread ) {
+                       pthread_cancel( * qry->thread );
+               }
+       }
+}
+
+/**
+ * Initialize LDAP query. This function should be called once before executing
+ * any LDAP queries to initialize thread specific data.
+ */
+void ldapqry_initialize( void ) {
+       /* printf( "ldapqry_initialize...\n" ); */
+       if( ! _queryThreadInit_ ) {
+               /*
+               printf( "ldapqry_initialize::creating thread specific area\n" );
+               */
+               pthread_key_create( &_queryThreadKey_, ldapqry_destroyer );
+               _queryThreadInit_ = TRUE;
+       }
+       /* printf( "ldapqry_initialize... done!\n" ); */
+}
+
+/**
+ * Age the query based on LDAP control parameters.
+ * \param qry    Query object to process.
+ * \param maxAge Maximum age of query (in seconds).
+ */
+void ldapqry_age( LdapQuery *qry, gint maxAge ) {
+       gint age;
+
+       g_return_if_fail( qry != NULL );
+
+       /* Limit the time that queries can hang around */       
+       if( maxAge < 1 ) maxAge = LDAPCTL_MAX_QUERY_AGE;
+
+       /* Check age of query */
+       age = time( NULL ) - qry->touchTime;
+       if( age > maxAge ) {
+               qry->agedFlag = TRUE;
+       }
+}
+
+/**
+ * Delete folder associated with query results.
+ * \param qry Query object to process.
+ */
+void ldapqry_delete_folder( LdapQuery *qry ) {
+       AddressCache *cache;
+       ItemFolder *folder;
+
+       g_return_if_fail( qry != NULL );
+
+       folder = qry->folder;
+       if( folder ) {
+               cache = qry->server->addressCache;
+               folder = addrcache_remove_folder_delete( cache, folder );
+               if( folder ) {
+                       addritem_free_item_folder( folder );
+               }
+               qry->folder = NULL;
+       }
+}
+
+#endif /* USE_LDAP */
+
+/*
+ * End of Source.
+ */
+
+
diff --git a/src/ldapquery.h b/src/ldapquery.h
new file mode 100644 (file)
index 0000000..34bb712
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions necessary to define an LDAP query.
+ */
+
+#ifndef __LDAPQUERY_H__
+#define __LDAPQUERY_H__
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+#include "ldapctrl.h"
+#include "ldapserver.h"
+#include "addritem.h"
+#include "addrcache.h"
+
+/*
+ * Constants.
+ */
+#define LDAPQUERY_NONE     0
+#define LDAPQUERY_STATIC   1
+#define LDAPQUERY_DYNAMIC  2
+
+/* Error codes */
+#define LDAPRC_SUCCESS    0
+#define LDAPRC_CONNECT    -1
+#define LDAPRC_INIT       -2
+#define LDAPRC_BIND       -3
+#define LDAPRC_SEARCH     -4
+#define LDAPRC_TIMEOUT    -5
+#define LDAPRC_CRITERIA   -6
+#define LDAPRC_NOENTRIES  -7
+
+typedef struct _LdapQuery LdapQuery;
+struct _LdapQuery {
+       LdapControl *control;
+       gint        retVal;
+       gint        queryType;
+       gchar       *queryName;
+       gchar       *searchValue;
+       gint        queryID;
+       gint        entriesRead;
+       gint        elapsedTime;
+       gboolean    stopFlag;
+       gboolean    busyFlag;
+       gboolean    agedFlag;
+       gboolean    completed;
+       time_t      touchTime;
+       pthread_t   *thread;
+       pthread_mutex_t *mutexStop;
+       pthread_mutex_t *mutexBusy;
+       pthread_mutex_t *mutexEntry;
+       void        (*callBackStart)( void * );
+       void        (*callBackEntry)( void *, void * );
+       void        (*callBackEnd)( void * );
+       ItemFolder  *folder;            /* Reference to folder in cache */
+       LdapServer  *server;            /* Reference to (parent) LDAP server */
+       // SyldapServer *server;                /* Reference to (parent) LDAP server */
+};
+
+/* Function prototypes */
+void ldapqry_initialize                ( void );
+LdapQuery *ldapqry_create      ( void );
+void ldapqry_set_control       ( LdapQuery *qry, LdapControl *ctl );
+void ldapqry_set_name          ( LdapQuery* qry, const gchar *value );
+void ldapqry_set_search_value  ( LdapQuery *qry, const gchar *value );
+void ldapqry_set_error_status  ( LdapQuery* qry, const gint value );
+void ldapqry_set_query_type    ( LdapQuery* qry, const gint value );
+void ldapqry_set_query_id      ( LdapQuery* qry, const gint value );
+void ldapqry_set_entries_read  ( LdapQuery* qry, const gint value );
+void ldapqry_set_callback_start        ( LdapQuery *qry, void *func );
+void ldapqry_set_callback_entry        ( LdapQuery *qry, void *func );
+void ldapqry_set_callback_end  ( LdapQuery *qry, void *func );
+void ldapqry_clear             ( LdapQuery *qry );
+void ldapqry_release_thread    ( LdapQuery *qry );
+void ldapqry_free              ( LdapQuery *qry );
+void ldapqry_print             ( const LdapQuery *qry, FILE *stream );
+void ldapqry_set_stop_flag     ( LdapQuery *qry, const gboolean value );
+gboolean ldapqry_get_stop_flag ( LdapQuery *qry );
+void ldapqry_set_busy_flag     ( LdapQuery *qry, const gboolean value );
+gboolean ldapqry_get_busy_flag ( LdapQuery *qry );
+void ldapqry_set_aged_flag     ( LdapQuery *qry, const gboolean value );
+gboolean ldapqry_get_aged_flag ( LdapQuery *qry );
+
+gboolean ldapqry_check_search  ( LdapQuery *qry );
+void ldapqry_touch             ( LdapQuery *qry );
+gint ldapqry_search            ( LdapQuery *qry );
+gint ldapqry_read_data_th      ( LdapQuery *qry );
+void ldapqry_join_thread       ( LdapQuery *qry );
+void ldapqry_cancel            ( LdapQuery *qry );
+void ldapqry_age               ( LdapQuery *qry, gint maxAge );
+void ldapqry_delete_folder     ( LdapQuery *qry );
+
+#endif /* USE_LDAP */
+
+#endif /* __LDAPQUERY_H__ */
diff --git a/src/ldapserver.c b/src/ldapserver.c
new file mode 100644 (file)
index 0000000..b091e53
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Functions necessary to access LDAP servers.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <sys/time.h>
+#include <string.h>
+#include <ldap.h>
+#include <lber.h>
+
+#include "mgutils.h"
+#include "addritem.h"
+#include "addrcache.h"
+#include "ldapctrl.h"
+#include "ldapquery.h"
+#include "ldapserver.h"
+#include "utils.h"
+#include "adbookbase.h"
+
+/**
+ * Create new LDAP server interface object.
+ * \return Initialized LDAP server object.
+ */
+LdapServer *ldapsvr_create() {
+       LdapServer *server;
+
+       server = g_new0( LdapServer, 1 );
+       server->type = ADBOOKTYPE_LDAP;
+       server->addressCache = addrcache_create();
+       server->retVal = MGU_SUCCESS;
+       server->control = ldapctl_create();
+       server->listQuery = NULL;
+       server->searchFlag = FALSE;
+       return server;
+}
+
+/**
+ * Return name of server.
+ * \param  server Server object.
+ * \return Name for server.
+ */
+gchar *ldapsvr_get_name( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, NULL );
+       return addrcache_get_name( server->addressCache );
+}
+
+/**
+ * Specify name to be used.
+ * \param server Server object.
+ * \param value      Name for server.
+ */
+void ldapsvr_set_name( LdapServer* server, const gchar *value ) {
+       g_return_if_fail( server != NULL );
+       addrcache_set_name( server->addressCache, value );
+}
+
+/**
+ * Refresh internal variables to force a file read.
+ * \param server Server object.
+ */
+void ldapsvr_force_refresh( LdapServer *server ) {
+       addrcache_refresh( server->addressCache );
+}
+
+/**
+ * Return status/error code.
+ * \param  server Server object.
+ * \return Status/error code.
+ */
+gint ldapsvr_get_status( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, -1 );
+       return server->retVal;
+}
+
+/**
+ * Return reference to root level folder.
+ * \param  server Server object.
+ * \return Root level folder.
+ */
+ItemFolder *ldapsvr_get_root_folder( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, NULL );
+       /*
+       printf( "ldapsvr_get_root_folder/start\n" );
+       ldapsvr_print_data( server, stdout );
+       printf( "ldapsvr_get_root_folder/done\n" );
+       */
+       return addrcache_get_root_folder( server->addressCache );
+}
+
+/**
+ * Test whether server data has been accessed.
+ * \param  server Server object.
+ * \return <i>TRUE</i> if data was accessed.
+ */
+gboolean ldapsvr_get_accessed( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, FALSE );
+       return server->addressCache->accessFlag;
+}
+
+/**
+ * Specify that server's data whas beed accessed.
+ * \param server Server object.
+ * \param value      Value for flag.
+ */
+void ldapsvr_set_accessed( LdapServer *server, const gboolean value ) {
+       g_return_if_fail( server != NULL );
+       server->addressCache->accessFlag = value;
+}
+
+/**
+ * Test whether server data has been modified.
+ * \param  server Server object.
+ * \return <i>TRUE</i> if data was modified.
+ */
+gboolean ldapsvr_get_modified( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, FALSE );
+       return server->addressCache->modified;
+}
+
+/**
+ * Specify modify flag.
+ * \param server Server object.
+ * \param value      Value for flag.
+ */
+void ldapsvr_set_modified( LdapServer *server, const gboolean value ) {
+       g_return_if_fail( server != NULL );
+       server->addressCache->modified = value;
+}
+
+/**
+ * Test whether data was read from server.
+ * \param server Server object.
+ * \return <i>TRUE</i> if data was read.
+ */
+gboolean ldapsvr_get_read_flag( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, FALSE );
+       return server->addressCache->dataRead;
+}
+
+/**
+ * Test whether server is to be used for dynamic searches.
+ * \param server Server object.
+ * \return <i>TRUE</i> if server is used for dynamic searches.
+ */
+gboolean ldapsvr_get_search_flag( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, FALSE );
+       return server->searchFlag;
+}
+
+/**
+ * Specify that server is to be used for dynamic searches.
+ * \param server Server object.
+ * \param value      Name for server.
+ */
+void ldapsvr_set_search_flag( LdapServer *server, const gboolean value ) {
+       g_return_if_fail( server != NULL );
+       server->searchFlag = value;
+}
+
+/**
+ * Specify the reference to control data that will be used for the query. The calling
+ * module should be responsible for creating and destroying this control object.
+ * \param server Server object.
+ * \param ctl    Control data.
+ */
+void ldapsvr_set_control( LdapServer *server, LdapControl *ctl ) {
+       g_return_if_fail( server != NULL );
+       addrcache_refresh( server->addressCache );
+       server->control = ctl;
+}
+
+/**
+ * Release LDAP control object.
+ * \param server Server object.
+ */
+static void ldapsvr_release_control( LdapServer *server ) {
+       g_return_if_fail( server != NULL );
+       ldapctl_free( server->control );
+       server->control = NULL;
+}
+
+/**
+ * Free up LDAP server interface object by releasing internal memory.
+ * \param server Server object.
+ */
+void ldapsvr_free( LdapServer *server ) {
+       g_return_if_fail( server != NULL );
+
+       /* Stop and cancel any queries that may be active */
+       ldapsvr_stop_all_query( server );
+       ldapsvr_cancel_all_query( server );
+
+       /* Clear cache */
+       addrcache_clear( server->addressCache );
+       addrcache_free( server->addressCache );
+
+       /* Free LDAP control block */
+       ldapctl_free( server->control );
+       server->control = NULL;
+
+       /* Clear pointers */
+       server->type = ADBOOKTYPE_NONE;
+       server->addressCache = NULL;
+       server->retVal = MGU_SUCCESS;
+       server->listQuery = NULL;
+       server->searchFlag = FALSE;
+
+       /* Now release LDAP object */
+       g_free( server );
+}
+
+/**
+ * Display object to specified stream.
+ * \param server Server object.
+ * \param stream     Output stream.
+ */
+void ldapsvr_print_data( LdapServer *server, FILE *stream ) {
+       GList *node;
+       gint  i;
+
+       g_return_if_fail( server != NULL );
+
+       fprintf( stream, "LdapServer:\n" );
+       fprintf( stream, "  ret val: %d\n", server->retVal );
+       fprintf( stream, "srch flag: %s\n",
+                       server->searchFlag ? "yes" : "no" );
+       if( server->control ) {
+               ldapctl_print( server->control, stream );
+       }
+       else {
+               fprintf( stream, "  control: NULL\n" );
+       }
+       addrcache_print( server->addressCache, stream );
+       addritem_print_item_folder( server->addressCache->rootFolder, stream );
+
+       /* Dump queries */
+       i = 1;
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+               fprintf( stream, "    query: %2d : %s\n", i, qry->queryName );
+               i++;
+               node = g_list_next( node );
+       }
+}
+
+/**
+ * Add query to server.
+ * \param server Server object.
+ * \param qry    Query object.
+ */
+void ldapsvr_add_query( LdapServer *server, LdapQuery *qry ) {
+       g_return_if_fail( server != NULL );
+       g_return_if_fail( qry != NULL );
+
+       server->listQuery = g_list_append( server->listQuery, qry );
+       qry->server = server;
+}
+
+/**
+ * Free all queries.
+ * \param server Server object.
+ */
+void ldapsvr_free_all_query( LdapServer *server ) {
+       GList *node;    
+       g_return_if_fail( server != NULL );
+
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+               ldapqry_free( qry );
+               node->data = NULL;
+               node = g_list_next( node );
+       }
+       g_list_free( server->listQuery );
+       server->listQuery = NULL;
+}
+
+/**
+ * Return link list of persons.
+ * \param server Server object.
+ * \return List of persons.
+ */
+GList *ldapsvr_get_list_person( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, NULL );
+       return addrcache_get_list_person( server->addressCache );
+}
+
+/**
+ * Return link list of folders. There are no "real" folders that are returned
+ * from the server.
+ * \param  server Server object.
+ * \return List of folders.
+ */
+GList *ldapsvr_get_list_folder( LdapServer *server ) {
+       g_return_val_if_fail( server != NULL, NULL );
+       /* return addrcache_get_list_folder( server->addressCache ); */
+       return NULL;
+}
+
+/**
+ * Execute specified query.
+ * \param server LDAP server.
+ * \param qry    LDAP query.
+ */
+void ldapsvr_execute_query( LdapServer *server, LdapQuery *qry ) {
+       LdapControl *ctlCopy;
+
+       g_return_if_fail( server != NULL );
+       g_return_if_fail( qry != NULL );
+
+       /* Copy server's control data to the query */
+       ctlCopy = ldapctl_create();
+       ldapctl_copy( server->control, ctlCopy );
+       ldapqry_set_control( qry, ctlCopy );
+       ldapqry_initialize();
+
+       /* Perform query */     
+       printf( "ldapsvr_execute_query::reading with thread...\n" );
+       if( ldapqry_check_search( qry ) ) {
+               ldapqry_read_data_th( qry );
+               if( qry->retVal == LDAPRC_SUCCESS ) {
+                       printf( "ldapsvr_execute_query::SUCCESS with thread...\n" );
+               }
+       }
+       printf( "ldapsvr_execute_query... terminated\n" );
+}
+
+/**
+ * Stop all queries for specified ID.
+ * \param server Server object.
+ * \param queryID    Query ID to stop.
+ */
+void ldapsvr_stop_query_id( LdapServer *server, const gint queryID ) {
+       GList *node;    
+       g_return_if_fail( server != NULL );
+
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+               if( qry->queryID == queryID ) {
+                       /* Notify thread to stop */
+                       ldapqry_set_stop_flag( qry, TRUE );
+               }
+               node = g_list_next( node );
+       }
+}
+
+/**
+ * Stop all queries by notifying each thread to stop.
+ * \param server Server object.
+ */
+void ldapsvr_stop_all_query( LdapServer *server ) {
+       GList *node;    
+       g_return_if_fail( server != NULL );
+
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+               ldapqry_set_stop_flag( qry, TRUE );
+               node = g_list_next( node );
+       }
+}
+
+/**
+ * Cancel all query threads for server.
+ * \param server Server object.
+ */
+void ldapsvr_cancel_all_query( LdapServer *server ) {
+       GList *node;    
+       g_return_if_fail( server != NULL );
+
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+               /* Notify thread to stop */
+               ldapqry_set_stop_flag( qry, TRUE );
+               /* Now cancel thread */
+               ldapqry_cancel( qry );
+               node = g_list_next( node );
+       }
+}
+
+/**
+ * Search most recent query for specified search term. The most recent
+ * completed query is returned. If no completed query is found, the most recent
+ * incomplete is returned.
+ * \param server LdapServer.
+ * \param searchTerm Search term to locate.
+ * \return Query object, or <i>NULL</i> if none found.
+ */
+LdapQuery *ldapsvr_locate_query( LdapServer *server, const gchar *searchTerm )
+{
+       LdapQuery *incomplete = NULL;
+       GList *node;    
+       g_return_if_fail( server != NULL );
+
+       node = server->listQuery;
+       node = g_list_last( node );
+       /* Search backwards for query */
+       while( node ) {
+               LdapQuery *qry = node->data;
+               if( g_strcasecmp( qry->searchValue, searchTerm ) == 0 ) {
+                       if( qry->agedFlag ) continue;
+                       if( qry->completed ) {
+                               /* Found */
+                               return qry;
+                       }
+                       if( ! incomplete ) {
+                               incomplete = qry;
+                       }
+               }
+               node = g_list_previous( node );
+       }
+       return incomplete;
+}
+
+/**
+ * Retire aged queries.
+ * \param server LdapServer.
+ */
+void ldapsvr_retire_query( LdapServer *server ) {
+       GList *node;
+       GList *listDelete;
+       GList *listQuery;
+       gint maxAge;
+       LdapControl *ctl;
+
+       g_return_if_fail( server != NULL );
+       ctl = server->control;
+       maxAge = ctl->maxQueryAge;
+
+       /* Identify queries to age and move to deletion list */
+       listDelete = NULL;
+       node = server->listQuery;
+       while( node ) {
+               LdapQuery *qry = node->data;
+
+               ldapqry_age( qry, maxAge );
+               if( qry->agedFlag ) {
+                       /* Delete folder associated with query */
+                       ldapqry_delete_folder( qry );
+                       listDelete = g_list_append( listDelete, qry );
+               }
+               node = g_list_next( node );
+       }
+
+       /* Delete queries */
+       listQuery = server->listQuery;
+       node = listDelete;
+       while( node ) {
+               LdapQuery *qry = node->data;
+
+               listQuery = g_list_remove( listQuery, qry );
+               ldapqry_free( qry );
+               node->data = NULL;
+               node = g_list_next( node );
+       }
+       server->listQuery = listQuery;
+
+       /* Free up deletion list */
+       g_list_free( listDelete );
+}
+
+#endif /* USE_LDAP */
+
+/*
+ * End of Source.
+ */
+
diff --git a/src/ldapserver.h b/src/ldapserver.h
new file mode 100644 (file)
index 0000000..e18e4d1
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Definitions necessary to access LDAP servers.
+ */
+
+#ifndef __LDAPSERVER_H__
+#define __LDAPSERVER_H__
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+
+#include "ldapctrl.h"
+#include "addritem.h"
+#include "addrcache.h"
+#include "adbookbase.h"
+
+#define MGU_LDAP_CONNECT   -51
+#define MGU_LDAP_INIT      -52
+#define MGU_LDAP_BIND      -53
+#define MGU_LDAP_SEARCH    -54
+#define MGU_LDAP_TIMEOUT   -55
+#define MGU_LDAP_CRITERIA  -56
+#define MGU_LDAP_NOENTRIES -57
+
+typedef struct _LdapServer LdapServer;
+struct _LdapServer {
+       AddressBookType type;
+       AddressCache    *addressCache;
+       gint            retVal;
+       LdapControl     *control;
+       gboolean        searchFlag;
+       GList           *listQuery;
+};
+
+/* Function prototypes */
+LdapServer *ldapsvr_create     ( void );
+void ldapsvr_set_name          ( LdapServer *server, const gchar *value );
+void ldapsvr_set_accessed      ( LdapServer *server, const gboolean value );
+void ldapsvr_force_refresh     ( LdapServer *server );
+void ldapsvr_free              ( LdapServer *server );
+gint ldapsvr_get_status                ( LdapServer *server );
+gboolean ldapsvr_get_accessed  ( LdapServer *server );
+gchar *ldapsvr_get_name                ( LdapServer *server );
+gboolean ldapsvr_get_modified  ( LdapServer *server );
+void ldapsvr_set_modified      ( LdapServer *server, const gboolean value );
+gboolean ldapsvr_get_read_flag ( LdapServer *server );
+gboolean ldapsvr_get_search_flag( LdapServer *server );
+void ldapsvr_set_search_flag   ( LdapServer *server, const gboolean value );
+
+void ldapsvr_print_data                ( LdapServer *server, FILE *stream );
+void ldapsvr_cancel_read       ( LdapServer *server );
+
+ItemFolder *ldapsvr_get_root_folder    ( LdapServer *server );
+GList *ldapsvr_get_list_person ( LdapServer *server );
+GList *ldapsvr_get_list_folder ( LdapServer *server );
+
+void ldapsvr_stop_query_id     ( LdapServer *server, const gint queryID );
+void ldapsvr_stop_all_query    ( LdapServer *server );
+void ldapsvr_cancel_all_query  ( LdapServer *server );
+void ldapsvr_retire_query      ( LdapServer *server );
+
+#endif /* USE_LDAP */
+
+#endif /* __LDAPSERVER_H__ */
+
diff --git a/src/ldaputil.c b/src/ldaputil.c
new file mode 100644 (file)
index 0000000..f7bbced
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003 Match Grun
+ *
+ * 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.
+ */
+
+/*
+ * Some utility functions to access LDAP servers.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifdef USE_LDAP
+
+#include <glib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <ldap.h>
+#include <lber.h>
+
+#define SYLDAP_TEST_FILTER   "(objectclass=*)"
+#define SYLDAP_SEARCHBASE_V2 "cn=config"
+#define SYLDAP_SEARCHBASE_V3 ""
+#define SYLDAP_V2_TEST_ATTR  "database"
+#define SYLDAP_V3_TEST_ATTR  "namingcontexts"
+
+/**
+ * Attempt to discover the base DN for a server using LDAP version 3.
+ * \param  ld  LDAP handle for a connected server.
+ * \param  tov Timeout value (seconds), or 0 for none, default 30 secs.
+ * \return List of Base DN's, or NULL if could not read. List should be
+ *         g_free() when done.
+ */
+static GList *ldaputil_test_v3( LDAP *ld, gint tov ) {
+       GList *baseDN = NULL;
+       gint rc, i;
+       LDAPMessage *result, *e;
+       gchar *attribs[2];
+       BerElement *ber;
+       gchar *attribute;
+       gchar **vals;
+       struct timeval timeout;
+
+       /* Set timeout */
+       timeout.tv_usec = 0L;
+       if( tov > 0 ) {
+               timeout.tv_sec = tov;
+       }
+       else {
+               timeout.tv_sec = 30L;
+       }
+
+       /* Test for LDAP version 3 */
+       attribs[0] = SYLDAP_V3_TEST_ATTR;
+       attribs[1] = NULL;
+       rc = ldap_search_ext_s(
+               ld, SYLDAP_SEARCHBASE_V3, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER,
+               attribs, 0, NULL, NULL, &timeout, 0, &result );
+
+       if( rc == LDAP_SUCCESS ) {
+               /* Process entries */
+               for( e = ldap_first_entry( ld, result );
+                    e != NULL;
+                    e = ldap_next_entry( ld, e ) ) 
+               {
+                       /* Process attributes */
+                       for( attribute = ldap_first_attribute( ld, e, &ber );
+                            attribute != NULL;
+                            attribute = ldap_next_attribute( ld, e, ber ) )
+                       {
+                               if( strcasecmp(
+                                       attribute, SYLDAP_V3_TEST_ATTR ) == 0 )
+                               {
+                                       vals = ldap_get_values( ld, e, attribute );
+                                       if( vals != NULL ) {
+                                               for( i = 0; vals[i] != NULL; i++ ) {
+                                                       baseDN = g_list_append(
+                                                               baseDN, g_strdup( vals[i] ) );
+                                               }
+                                       }
+                                       ldap_value_free( vals );
+                               }
+                               ldap_memfree( attribute );
+                       }
+                       if( ber != NULL ) {
+                               ber_free( ber, 0 );
+                       }
+                       ber = NULL;
+               }
+       }
+       ldap_msgfree( result );
+       return baseDN;
+}
+
+/**
+ * Attempt to discover the base DN for a server using LDAP version 2.
+ * \param  ld  LDAP handle for a connected server.
+ * \param  tov Timeout value (seconds), or 0 for none, default 30 secs.
+ * \return List of Base DN's, or NULL if could not read. List should be
+ *         g_free() when done.
+ */
+static GList *ldaputil_test_v2( LDAP *ld, gint tov ) {
+       GList *baseDN = NULL;
+       gint rc, i;
+       LDAPMessage *result, *e;
+       gchar *attribs[1];
+       BerElement *ber;
+       gchar *attribute;
+       gchar **vals;
+       struct timeval timeout;
+
+       /* Set timeout */
+       timeout.tv_usec = 0L;
+       if( tov > 0 ) {
+               timeout.tv_sec = tov;
+       }
+       else {
+               timeout.tv_sec = 30L;
+       }
+
+       attribs[0] = NULL;
+       rc = ldap_search_ext_s(
+               ld, SYLDAP_SEARCHBASE_V2, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER,
+               attribs, 0, NULL, NULL, &timeout, 0, &result );
+
+       if( rc == LDAP_SUCCESS ) {
+               /* Process entries */
+               for( e = ldap_first_entry( ld, result );
+                    e != NULL;
+                    e = ldap_next_entry( ld, e ) )
+             &