/*-
 * Copyright (c) 2003-2004 Seth Kingsley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *    This product includes software developed by Seth Kingsley and
 *    contributors.
 * 4. Neither the author's name, nor any of the contributors names may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include    <sys/param.h>
#include    <sys/types.h>
#include    <sys/cdefs.h>
#include    <sys/fcntl.h>

#include    <gtk/gtkmain.h>
#include    <gtk/gtkdialog.h>
#include    <gtk/gtkprogressbar.h>
#include    <gtk/gtklabel.h>

#include    <lib.h>

#include    <stdio.h>
#include    <dirent.h>
#include    <string.h>
#include    <assert.h>
#include    <sysexits.h>
#ifdef DEBUG
#include    <err.h>
#endif /* DEBUG */

#include    <xmk-gtk.h>

#include    "support.h"
#include    "bpm-gui.h"
#include    "portsman.h"
#include    "catlist.h"
#include    "util.h"
#include    "refresh.h"

__RCSID("$Id: refresh.c,v 1.32 2004/08/24 02:40:08 sethk Exp $");

/* XXX use libinstall/lib.h */
#define	INDEX_FN    PORTS_BASE "/INDEX"
#define INDEX_MAXLN 4096
#define DB_DIR	    "/var/db/pkg"
#define ITER_BYTES  (128 * 1024)
#define ITER_ENTS   32

#define ALL_CAT	    _("*** ALL ***")
#define INST_CAT    _("*** INSTALLED ***")
#define STALE_CAT   _("*** STALE ***")
#define SEARCH_CAT  _("(Search)")

#define TERM_PROGS  {"gnome-terminal", "rxvt", "xterm", NULL}
#ifdef DEBUG
#define CON_SCRIPT  SRC_DIR "/../scripts/term.csh"
#else /* !DEBUG */
#define CON_SCRIPT  PREFIX "/libexec/" PACKAGE "/term"
#endif /* DEBUG */

enum refresh_state
{
    RSTATE_INDEX_OPEN,
    RSTATE_INDEX,
    RSTATE_INDEX_CLOSE,
    RSTATE_DB_OPEN,
    RSTATE_DB,
    RSTATE_DB_CLOSE,
    RSTATE_DONE
};

struct refresh_data
{
    GtkWindow		    *rd_app;
    struct catlist_data	    *rd_cdatap;
    GtkDialog		    *rd_refresh_dlg,
			    *rd_search_dlg;
    GtkEntry		    *rd_search_ent,
			    *rd_search_dlg_ent;
    GtkMenuItem		    *rd_clear_mitem;
    GtkButton		    *rd_clear_tool;
    enum refresh_state	    rd_state;
    FILE		    *rd_filep;
    off_t		    rd_nbytes,
			    rd_rbytes;
    u_int		    rd_linec;
    int			    rd_dirfd;
    size_t		    rd_entsize;
    u_char		    *rd_entbuf;
    int			    rd_ebytes;
    float		    rd_ptotal,
			    rd_percent;
    GtkProgressBar	    *rd_progress;
    GtkStatusbar	    *rd_status_bar;
    GSList		    *rd_cdlist;
    guint		    rd_ncat;
    struct cat_desc	    *rd_allcat,
			    *rd_instcat,
			    *rd_stalecat,
			    *rd_srchcat;
};

struct refresh_search_data
{
    const gchar		*rsd_text;
    struct refresh_data	*rsd_rdatap;
};

struct refresh_data *rdatap;

static gint
_refresh_catcmp(struct cat_desc *cda, struct cat_desc *cdb)
{
    return strcasecmp(cda->cd_name, cdb->cd_name);
}

static gint
_refresh_port_cmp(const gchar *a, const gchar *b)
{
    return -strcmp(a, b);
}

static void
_refresh_portadd(struct refresh_data *rdp,
	struct cat_desc *cdescp,
	struct port_desc *pdescp)
{
    assert(pdescp->pd_name);
    g_tree_insert(cdescp->cd_pdtree, pdescp->pd_name, pdescp);
}

static gboolean
_refresh_parse_name(gchar *pname, gchar **namep, gchar **verp)
{
    gchar   *cp;

    if (!(cp = strrchr(pname, '-')))
    {
#ifdef DEBUG
	warnx("portname missing version component");
#endif /* DEBUG */
	return FALSE;
    }
    *cp++ = '\0';

    *namep = g_strdup(pname);
    *verp = g_strdup(cp);

    return TRUE;
}

static gboolean
_refresh_index_parse(gchar *line,
	struct port_desc *portdp,
	struct refresh_data *rdp)
{
    gchar   *sp;
    guint   i;

    for (i = 0, sp = line; *sp; ++sp)
	if (*sp == '|')
	    ++i;

    if (i != (PFIELD_NFIELD - 1))
    {
#ifdef DEBUG
	warnx("invalid number of fields");
#endif /* DEBUG */
	return FALSE;
    }

    portdp->pd_state = PSTATE_UNINST;

    for (i = 0; i < PFIELD_NFIELD; ++i)
    {
	if (i < PFIELD_NFIELD - 1)
	{
	    if (!(sp = strchr(line, '|')))
		break;
	    *sp++ = '\0';
	}

	switch (i)
	{
	    gchar   **names;
	    guint   j;
	    size_t  clen;
	    GError  *errp;

	    case PFIELD_NAME:
		if (_refresh_parse_name(line,
			    &(portdp->pd_name),
			    &(portdp->pd_version)) == FALSE)
		    return FALSE;
		break;

	    case PFIELD_ORIGIN:
		if (!strncmp(line, PORTS_BASE "/", sizeof(PORTS_BASE)))
		    line+= sizeof(PORTS_BASE);
		portdp->pd_origin = g_strdup(line);
		break;

	    case PFIELD_PREFIX:
		portdp->pd_prefix = g_strdup(line);
		break;

	    case PFIELD_COMM:
		clen = strlen(line);
		if ((line[0] == '"' || line[0] == '\'') &&
			line[clen - 1] == line[0])
		{
		    line[clen - 1] = '\0';
		    ++line;
		}
		portdp->pd_comm = g_convert(line, clen,
			"UTF-8", "ISO-8859-1",
			NULL, NULL,
			&errp);
		break;

	    case PFIELD_DESCFN:
		portdp->pd_descpath = g_strdup(line);
		assert(!strchr(line, ' '));
		break;

	    case PFIELD_MAINT:
		portdp->pd_maint = g_strdup(line);
		break;

	    case PFIELD_CATS:
		names = g_strsplit(line, " ", 0);
		portdp->pd_ncat = util_numstrv(names);
		portdp->pd_cats = g_new(struct cat_desc *, portdp->pd_ncat);
		for (j = 0; j < portdp->pd_ncat; ++j)
		{
		    portdp->pd_cats[j] = refresh_cat_find(rdp,
			    names[j],
			    FALSE,
			    FALSE);
		    g_tree_insert(portdp->pd_cats[j]->cd_pdtree,
			    portdp->pd_name,
			    portdp);
		}
		g_free(names);
		break;

	    case PFIELD_WEBSITE:
		portdp->pd_website = g_strdup(line);
		break;

	    default:
		break;
	}

	line = sp;
    }

    return TRUE;
}

static void
_refresh_portfree(struct port_desc *portdp)
{
    g_free(portdp->pd_name);
    g_free(portdp->pd_version);
    g_free(portdp->pd_origin);
    g_free(portdp->pd_prefix);
    g_free(portdp->pd_comm);
    g_free(portdp->pd_descpath);
    g_free(portdp->pd_maint);
    if (portdp->pd_ncat)
	g_free(portdp->pd_cats);
    g_free(portdp->pd_website);
    g_free(portdp);
}

static gboolean
_refresh_index_iter(struct refresh_data *rdp)
{
    gchar   ln[INDEX_MAXLN + 1];
    ssize_t rb;
    gchar   prog_txt[32 + 1];

    for (rb = 0; rb < ITER_BYTES;)
    {
	struct port_desc    *portdp;

	if (!fgets(ln, sizeof(ln), rdp->rd_filep))
	{
	    ++rdp->rd_state;
	    return TRUE;
	}
	rb+= strlen(ln);
	g_strchomp(ln);
	++rdp->rd_linec;

	portdp = g_new(struct port_desc, 1);

	if (_refresh_index_parse(ln, portdp, rdp) != TRUE)
	{
	    _refresh_portfree(portdp);
#ifdef DEBUG
	    warnx("%s:%u: invalid format", INDEX_FN, rdp->rd_linec);
#endif /* DEBUG */
	    continue;
	}

	_refresh_portadd(rdp, rdp->rd_allcat, portdp);
    }
    rdp->rd_rbytes+= rb;

    snprintf(prog_txt, sizeof(prog_txt), _("%u ports"),
	    g_tree_nnodes(rdp->rd_allcat->cd_pdtree));
    gtk_progress_bar_set_text(rdp->rd_progress, prog_txt);

    return TRUE;
}

static void
_refresh_db_parse(struct dirent *dp, struct refresh_data *rdp)
{
    struct port_desc	portd,
			*portdp;

    if (dp->d_type != DT_DIR || dp->d_name[0] == '.')
	return;

    if (_refresh_parse_name(dp->d_name,
		&(portd.pd_name),
		&(portd.pd_version)) == FALSE)
	return;

    portdp = g_tree_search(rdp->rd_allcat->cd_pdtree,
	    (GCompareFunc)_refresh_port_cmp,
	    portd.pd_name);
    if (!portdp)
    {
#ifdef DEBUG_INDEX
	warnx("port %s not found", portd.pd_name);
#endif /* INDEX_DEUBG */
	return;
    }

    switch (version_cmp(portd.pd_version, portdp->pd_version))
    {
	case -1:
#ifdef DEBUG_INDEX
	    warnx("%s is stale", portdp->pd_name);
#endif /* DEBUG_INDEX */
	    portdp->pd_state = PSTATE_STALE;
	    _refresh_portadd(rdp, rdp->rd_stalecat, portdp);
	    break;

	default:
	    portdp->pd_state = PSTATE_INST;
    }

    _refresh_portadd(rdp, rdp->rd_instcat, portdp);
}

static gboolean
_refresh_db_iter(struct refresh_data *rdp)
{
    guint   nent;
    gchar   prog_txt[32 + 1];

    for (nent = 0; nent < ITER_ENTS; ++nent)
    {
	struct dirent	*dp;

	if (!rdp->rd_ebytes)
	{
	    if ((rdp->rd_ebytes = getdents(rdp->rd_dirfd,
			    rdp->rd_entbuf,
			    rdp->rd_entsize)) <= 0)
	    {
		if (rdp->rd_ebytes == -1)
		{
#ifdef DEBUG
		    warn("getdents");
#endif /* DEBUG */
		}

		++rdp->rd_state;
		return TRUE;
	    }
	}

	dp = (struct dirent *)rdp->rd_entbuf;
	_refresh_db_parse(dp, rdp);

	rdp->rd_rbytes+= dp->d_reclen;
	if ((rdp->rd_ebytes-= dp->d_reclen))
	    bcopy(rdp->rd_entbuf + dp->d_reclen,
		    rdp->rd_entbuf,
		    rdp->rd_ebytes);
    }

    snprintf(prog_txt, sizeof(prog_txt), _("%u installed"),
	    g_tree_nnodes(rdp->rd_instcat->cd_pdtree));
    gtk_progress_bar_set_text(rdp->rd_progress, prog_txt);

    return TRUE;
}

static gboolean
_refresh_index_open(struct refresh_data *rdp)
{
    struct stat	st;

    if (!(rdp->rd_filep = fopen(INDEX_FN, "r")))
    {
	xmk_warning(GTK_WINDOW(rdp->rd_refresh_dlg), TRUE, _("Open %s"),
		INDEX_FN);
	return FALSE;
    }

    if (fstat(fileno(rdp->rd_filep), &st) == -1)
    {
	xmk_warning(GTK_WINDOW(rdp->rd_refresh_dlg), TRUE, _("FD stat"));
	fclose(rdp->rd_filep);
	return FALSE;
    }

    if (st.st_size == 0)
    {
	xmk_warning(GTK_WINDOW(rdp->rd_refresh_dlg),
		FALSE,
		_("Empty INDEX file"));
	fclose(rdp->rd_filep);
	return FALSE;
    }

    rdp->rd_nbytes = st.st_size;
    rdp->rd_rbytes = 0;
    rdp->rd_linec = 1;

    ++rdp->rd_state;

    gtk_statusbar_push(rdp->rd_status_bar,
	    0,
	    _("Reading port index"));

    return TRUE;
}

static gboolean
_refresh_index_close(struct refresh_data *rdp)
{
    fclose(rdp->rd_filep);

    ++rdp->rd_state;

    gtk_statusbar_pop(rdp->rd_status_bar, 0);

    return TRUE;
}

static gboolean
_refresh_db_open(struct refresh_data *rdp)
{
    struct stat	st;

    if ((rdp->rd_dirfd = open(DB_DIR, O_RDONLY)) == -1)
    {
#ifdef DEBUG
	warn("%s", DB_DIR);
#endif /* DEBUG */
	return FALSE;
    }

    if (fstat(rdp->rd_dirfd, &st) == -1)
    {
#ifdef DEBUG
	warn("fstat %s", DB_DIR);
#endif /* DEBUG */
	return FALSE;
    }

    rdp->rd_nbytes = st.st_size;
    rdp->rd_rbytes = 0;
    rdp->rd_entsize = MAX(sizeof(struct dirent), st.st_blksize);
    rdp->rd_entbuf = g_malloc(rdp->rd_entsize);
    rdp->rd_ebytes = 0;

    ++rdp->rd_state;

    gtk_statusbar_push(rdp->rd_status_bar,
	    0,
	    _("Reading installed ports"));

    return TRUE;
}

static gboolean
_refresh_db_close(struct refresh_data *rdp)
{
    close(rdp->rd_dirfd);

    g_free(rdp->rd_entbuf);

    ++rdp->rd_state;

    gtk_statusbar_pop(rdp->rd_status_bar, 0);

    return TRUE;
}

static gboolean
_refresh_iter(struct refresh_data *rdp)
{
    gboolean	ret;

    switch (rdp->rd_state)
    {
	case RSTATE_INDEX_OPEN:
	    rdp->rd_percent = 0.75;
	    return _refresh_index_open(rdp);

	case RSTATE_INDEX:
	    ret = _refresh_index_iter(rdp);
	    break;

	case RSTATE_INDEX_CLOSE:
	    rdp->rd_ptotal+= rdp->rd_percent;
	    rdp->rd_percent = 0.25;
	    return _refresh_index_close(rdp);

	case RSTATE_DB_OPEN:
	    return _refresh_db_open(rdp);

	case RSTATE_DB:
	    ret = _refresh_db_iter(rdp);
	    break;

	case RSTATE_DB_CLOSE:
	    rdp->rd_ptotal+= rdp->rd_percent;
	    rdp->rd_percent = 0.0;
	    return _refresh_db_close(rdp);

	case RSTATE_DONE:
	    gtk_dialog_response(rdp->rd_refresh_dlg, GTK_RESPONSE_OK);
	    ret = FALSE;
	    break;

	default:
	    assert(0);
	    ret = FALSE;
    }

    gtk_progress_bar_set_fraction(rdp->rd_progress,
	    rdp->rd_ptotal +
	    ((gdouble)rdp->rd_rbytes / rdp->rd_nbytes) * rdp->rd_percent);

    return ret;
}

static GTree *
_refresh_pdtree_new(void)
{
    return g_tree_new((GCompareFunc)strcasecmp);
}

static struct cat_desc *
_refresh_cat_new(const gchar *name, gboolean dynamic)
{
    struct cat_desc *cdescp = g_new(struct cat_desc, 1);

    cdescp->cd_name = g_strdup(name);
    if (cdescp->cd_name[0] != '*')
    {
	gchar	    *dir;
	struct stat st;

	dir = g_strdup_printf("%s/%s", PORTS_BASE, cdescp->cd_name);
#ifdef DEBUG_INDEX
	warnx("checking for %s/", dir);
#endif /* DEBUG_INDEX */
	cdescp->cd_real = (stat(dir, &st) != 0 && S_ISDIR(st.st_mode)) ?
	    TRUE : FALSE;
	cdescp->cd_dynamic = dynamic;
	g_free(dir);
    }
    else
	cdescp->cd_real = FALSE;

    cdescp->cd_pdtree = _refresh_pdtree_new();

    return cdescp;
}

static void
_refresh_cat_free(struct cat_desc *cdescp)
{
    g_free(cdescp->cd_name);
    g_tree_destroy(cdescp->cd_pdtree);
}

static GSList *
_refresh_clear_cats(GSList *cdlist)
{
    GSList  *gslp;

    for (gslp = cdlist; gslp; gslp = gslp->next)
	_refresh_cat_free(gslp->data);

    g_slist_free(gslp);

    return NULL;
}

static gboolean
_refresh_port_free(gchar *name, struct port_desc *pdescp, gpointer udata)
{
    _refresh_portfree(pdescp);

    return FALSE;
}

static gboolean
_refresh_port_reset(gchar *name, struct port_desc *pdescp, gpointer udata)
{
    pdescp->pd_state = PSTATE_UNINST;

    return FALSE;
}

static GSList *
_refresh_clear_ports(GTree *pdtree)
{
    g_tree_foreach(pdtree, (GTraverseFunc)_refresh_port_free, NULL);

    return NULL;
}

static void
_refresh_clear(struct refresh_data *rdp)
{
    if (rdp->rd_ncat)
    {
	catlist_clear(rdp->rd_cdatap);
	_refresh_clear_ports(rdp->rd_allcat->cd_pdtree);

	rdp->rd_cdlist = _refresh_clear_cats(rdp->rd_cdlist);
	rdp->rd_ncat = 0;
    }
}

gboolean
_refresh_search_traverse(gchar *name,
	struct port_desc *pdescp,
	struct refresh_search_data *rsdp)
{
    struct refresh_data	*rdp = rsdp->rsd_rdatap;

    if (strcasestr(name, rsdp->rsd_text) ||
	    strcasestr(pdescp->pd_comm, rsdp->rsd_text))
    {
	_refresh_portadd(rdp, rdp->rd_srchcat, pdescp);
#ifdef DEBUG_SEARCH
	fputc('+', stderr);
#endif /* DEBUG_SEARCH */
    }
#ifdef DEBUG_SEARCH
    else
	fputc('.', stderr);
#endif /* DEBUG_SEARCH */

    return FALSE;
}

gboolean
_refresh_search(struct refresh_data *rdp, GtkEntry *entry)
{
    struct refresh_search_data	rsdata;

    rsdata.rsd_rdatap = rdp;
    if (!*(rsdata.rsd_text = gtk_entry_get_text(entry)))
    {
	xmk_warning(rdp->rd_app, FALSE, _("Please enter a search term."));
	return FALSE;
    }

    if (rdp->rd_srchcat)
	refresh_search_clear(rdp);

    rdp->rd_srchcat = _refresh_cat_new(SEARCH_CAT, FALSE);
#ifdef DEBUG_SEARCH
    fprintf(stderr, "Searching: ");
#endif /* DEBUG_SEARCH */
    g_tree_foreach(rdp->rd_allcat->cd_pdtree,
	    (GTraverseFunc)_refresh_search_traverse,
	    &rsdata);
#ifdef DEBUG_SEARCH
    fputc('\n', stderr);
#endif /* DEBUG_SEARCH */

    if (!g_tree_nnodes(rdp->rd_srchcat->cd_pdtree))
    {
	xmk_warning(rdp->rd_app, FALSE, _("No ports found for term \"%s\""),
		rsdata.rsd_text);
	_refresh_cat_free(rdp->rd_srchcat);
	rdp->rd_srchcat = NULL;
	return FALSE;
    }

    catlist_add(rdp->rd_cdatap, rdp->rd_srchcat, TRUE);

    gtk_entry_set_text(entry, "");

    gtk_widget_set_sensitive(GTK_WIDGET(rdp->rd_clear_mitem), TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(rdp->rd_clear_tool), TRUE);

    return TRUE;
}

static gboolean
_refresh_dialog_run(struct refresh_data *rdp)
{
    guint	iid;
    gboolean	st = TRUE;

    rdp->rd_ptotal = 0.0;
    rdp->rd_percent = 1.0;
    gtk_progress_bar_set_fraction(rdp->rd_progress, rdp->rd_ptotal);

    iid = gtk_idle_add((GtkFunction)_refresh_iter, rdp);

    if (gtk_dialog_run(rdp->rd_refresh_dlg) != GTK_RESPONSE_OK)
    {
#ifdef DEBUG
	warnx("cancelled");
#endif /* DEBUG */

	st = FALSE;

	gtk_idle_remove(iid);

	xmk_busy_push(GTK_WIDGET(rdp->rd_refresh_dlg)->window);

	switch (rdp->rd_state)
	{
	    case RSTATE_INDEX_OPEN:
	    case RSTATE_DB_OPEN:
		break;

	    case RSTATE_INDEX:
	    case RSTATE_INDEX_CLOSE:
		_refresh_index_close(rdp);
		break;

	    case RSTATE_DB:
	    case RSTATE_DB_CLOSE:
		_refresh_db_close(rdp);
		break;

	    default:
		assert(0);
	}

	gtk_statusbar_push(rdp->rd_status_bar, 0, _("Cancelled..."));

	_refresh_clear(rdp);

	xmk_busy_pop(GTK_WIDGET(rdp->rd_refresh_dlg)->window);
    }

    gtk_widget_hide(GTK_WIDGET(rdp->rd_refresh_dlg));

    return st;
}

struct refresh_data *
refresh_init(GtkWindow *app, struct portsman_data *pdp)
{
    static struct refresh_data	rdata;

    rdata.rd_app = app;
    rdata.rd_cdatap = catlist_init(app, &rdata);

    rdata.rd_refresh_dlg = GTK_DIALOG(create_refresh_dlg());
    gtk_window_set_transient_for(GTK_WINDOW(rdata.rd_refresh_dlg), app);
    rdata.rd_search_dlg = GTK_DIALOG(create_search_dlg());
    gtk_window_set_transient_for(GTK_WINDOW(rdata.rd_search_dlg), app);
    rdata.rd_search_ent = GTK_ENTRY(lookup_widget(GTK_WIDGET(app),
		"search_ent"));
    rdata.rd_search_dlg_ent =
	GTK_ENTRY(lookup_widget(GTK_WIDGET(rdata.rd_search_dlg),
		"search_ent"));
    rdata.rd_clear_mitem = GTK_MENU_ITEM(lookup_widget(GTK_WIDGET(app),
		"clear_mitem"));
    rdata.rd_clear_tool = GTK_BUTTON(lookup_widget(GTK_WIDGET(app),
		"clear_tool"));

    rdata.rd_state = 0;

    rdata.rd_progress = GTK_PROGRESS_BAR(
	    lookup_widget(GTK_WIDGET(rdata.rd_refresh_dlg),
		"progress"));
    rdata.rd_status_bar = GTK_STATUSBAR(
	    lookup_widget(GTK_WIDGET(rdata.rd_refresh_dlg), "status_bar"));

    rdata.rd_cdlist = NULL;
    rdata.rd_ncat = 0;

    gtk_idle_add((GtkFunction)refresh_all, &rdata);

    return (rdatap = &rdata);
}

void
refresh_fini(struct refresh_data *rdp)
{
    catlist_fini(rdp->rd_cdatap);

    _refresh_clear(rdp);
}

gboolean
refresh_all(struct refresh_data *rdp)
{
    _refresh_clear(rdp);

    rdp->rd_state = RSTATE_INDEX_OPEN;

    rdp->rd_allcat = refresh_cat_find(rdp, ALL_CAT, FALSE, FALSE);
    rdp->rd_instcat = refresh_cat_find(rdp, INST_CAT, FALSE, TRUE);
    rdp->rd_stalecat = refresh_cat_find(rdp, STALE_CAT, FALSE, TRUE);
    rdp->rd_srchcat = NULL;

    if (_refresh_dialog_run(rdp))
	catlist_populate(rdp->rd_cdatap, rdp->rd_cdlist);

    return FALSE;
}

gboolean
refresh_db(struct refresh_data *rdp)
{
    rdp->rd_state = RSTATE_DB_OPEN;

    g_tree_foreach(rdp->rd_allcat->cd_pdtree,
	    (GTraverseFunc)_refresh_port_reset,
	    NULL);
    g_tree_destroy(rdp->rd_instcat->cd_pdtree);
    rdp->rd_instcat->cd_pdtree = _refresh_pdtree_new();

    g_tree_destroy(rdp->rd_stalecat->cd_pdtree);
    rdp->rd_stalecat->cd_pdtree = _refresh_pdtree_new();

    _refresh_dialog_run(rdp);

    catlist_refresh(rdp->rd_cdatap);

    return FALSE;
}

struct cat_desc *
refresh_cat_find(struct refresh_data *rdp,
	const gchar *name,
	gboolean search,
	gboolean dynamic)
{
    gchar	    *name_copy = g_strdup(name);
    struct cat_desc tcdesc, *cdescp;
    GSList	    *cdlist;

    tcdesc.cd_name = name_copy;

    cdlist = g_slist_find_custom(rdp->rd_cdlist,
	    &tcdesc,
	    (GCompareFunc)(_refresh_catcmp));

    g_free(name_copy);

    if (cdlist)
	cdescp = cdlist->data;
    else if (search == FALSE)
    {
	cdescp = _refresh_cat_new(name, dynamic);

	rdp->rd_cdlist = g_slist_insert_sorted(rdp->rd_cdlist,
		cdescp,
		(GCompareFunc)_refresh_catcmp);

	++rdp->rd_ncat;
    }
    else
	return NULL;

    return cdescp;
}

void
refresh_console(struct refresh_data *rdp)
{
    char	*argv[] = {NULL, "-e", CON_SCRIPT, NULL},
		*tprogs[] = TERM_PROGS;
    const gchar	*path;
    gchar	dir[MAXPATHLEN];

    if ((path = catlist_getpath(rdp->rd_cdatap)))
	g_snprintf(dir, sizeof(dir), "%s/%s", PORTS_BASE, path);
    else
	strlcpy(dir, PORTS_BASE, sizeof(dir));

    util_spawn("terminal", dir, tprogs, argv);
}

void
refresh_search(struct refresh_data *rdp)
{
    _refresh_search(rdp, rdp->rd_search_ent);
}

void
refresh_search_dialog(struct refresh_data *rdp)
{
    gtk_window_set_focus(GTK_WINDOW(rdp->rd_search_dlg),
	    GTK_WIDGET(rdp->rd_search_dlg_ent));
    gtk_widget_show(GTK_WIDGET(rdp->rd_search_dlg));
}

void
refresh_search_dialog_response(struct refresh_data *rdp, GtkResponseType resp)
{
    if (resp != GTK_RESPONSE_OK ||
	    _refresh_search(rdp, rdp->rd_search_dlg_ent))
	gtk_widget_hide(GTK_WIDGET(rdp->rd_search_dlg));
}

void
refresh_search_clear(struct refresh_data *rdp)
{
    assert(rdp->rd_srchcat);

    gtk_widget_set_sensitive(GTK_WIDGET(rdp->rd_clear_mitem), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(rdp->rd_clear_tool), FALSE);

    catlist_remove(rdp->rd_cdatap, rdp->rd_srchcat);

    _refresh_cat_free(rdp->rd_srchcat);
    rdp->rd_srchcat = NULL;
}

guint
refresh_numport(struct refresh_data *rdp)
{
    return g_tree_nnodes(rdp->rd_allcat->cd_pdtree);
}

guint
refresh_numcat(struct refresh_data *rdp)
{
    return rdp->rd_ncat;
}

