/*-
 * 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/cdefs.h>
#include    <sys/param.h>
#include    <sys/wait.h>
#include    <sys/errno.h>

#include    <gtk/gtkbin.h>
#include    <gtk/gtkstock.h>
#include    <gtk/gtkseparatormenuitem.h>
#include    <gtk/gtkimage.h>

#include    <unistd.h>
#include    <stdio.h>
#include    <signal.h>
#include    <string.h>
#include    <ctype.h>
#include    <assert.h>
#ifdef DEBUG
#include    <err.h>
#endif /* DEBUG */

#include    <xmk-gtk.h>

#include    "support.h"
#include    "bpm-gui.h"
#include    "portsman.h"
#include    "refresh.h"
#include    "portlist.h"
#include    "log.h"
#include    "install.h"

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

#define STOCK_PRFX  "gtk-"
#define INSTACT_KEY "install-action"
#define LINE_PRFXS  {"===> ", ">> ", "*** ", "---> "}
#ifdef DEBUG
#define SU_CMD	    SRC_DIR "/../gtk-su/obj/gtk-su root -c"
#define FETCH_CMD   SRC_DIR "/../fetch-wrapper/obj/fetch-wrapper"
#else /* !DEBUG */
#define SU_CMD	    "gtk-su root -c"
#define FETCH_CMD   PREFIX "/libexec/" PACKAGE "/fetch-wrapper"
#endif /* DEBUG */
#if __FreeBSD_version >= 500039
#define PKG_SUFX    ".tbz"
#else /* __FreeBSD_version < 500039 */
#define PKG_SUFX    ".tgz"
#endif /* __FreeBSD_version >= 500039 */

struct install_data
{
    struct refresh_data	    *id_rdatap;
    struct portlist_data    *id_pldatap;
    struct log_data	    *id_ldatap;
    GtkWindow		    *id_app;
    struct install_actdesc
    {
	enum install_action ia_action;
	const gchar	    *ia_target,
			    *ia_presverb,   /* Present tense */
			    *ia_progverb,   /* Progressive */
			    *ia_pastverb,   /* Past Tense */
			    *ia_label,
			    *ia_pixmap;
	GtkImageMenuItem    *ia_mitem,
			    *ia_tool;
    }			    *id_actions,
			    *id_curact;
    GtkMenuItem		    *id_mitem;
    GtkMenu		    *id_menu;
    GtkButton		    *id_action_ctool;
    GtkMenu		    *id_popup;
    GtkWindow		    *id_action_win;
    GtkImage		    *id_action_img;
    GtkLabel		    *id_action_lbl;
    GtkProgressBar	    *id_action_prog;
    GtkStatusbar	    *id_action_sbar,
			    *id_fetch_sbar;
    GtkButton		    *id_action_btn;
    GtkImage		    *id_action_btn_img;
    GtkLabel		    *id_action_btn_lbl;
    GtkToggleButton	    *id_details_btn;
    GtkScrolledWindow	    *id_details_sw;
    GtkTextView		    *id_details_text;
    GtkTextBuffer	    *id_details_buf;
    GtkButton		    *id_action_tool;
    GtkImage		    *id_action_tool_img;
    GtkLabel		    *id_action_tool_lbl;
    GtkFileChooserDialog    *id_package_dlg;
    enum install_action	    id_action_act;
    struct port_desc	    *id_action_port;
    pid_t		    id_action_pid;
    int			    id_action_pipe;
    guint		    id_action_iid;
    struct mk_lines	    *id_lines;
    gboolean		    id_fetching;
    char		    id_fetch_fname[64 + 1];
    off_t		    id_fetch_size;
};

struct install_data *idatap;

static struct install_actdesc *
_install_action_find(struct install_data *idp, enum install_action ia)
{
    u_int   i;

    if (ia == INSTACT_CURRENT)
    {
	assert(idp->id_curact);
	return idp->id_curact;
    }

    for (i = 0; idp->id_actions[i].ia_action != INSTACT_NONE; ++i)
	if (idp->id_actions[i].ia_action == ia)
	    return &(idp->id_actions[i]);

    return NULL;
}

static void
_install_image_set_pixmap(GtkImage *image,
	const gchar *name,
	GtkIconSize size)
{
    if (!strncmp(name, STOCK_PRFX, sizeof(STOCK_PRFX) - 1))
	gtk_image_set_from_stock(image, name, size);
    else
    {
	guint	    height;
	gchar	    *fn;
	GtkImage    *pix_image;
	GdkPixbuf   *pixbuf;

	switch (size)
	{
	    case GTK_ICON_SIZE_MENU: height = 16; break;
	    case GTK_ICON_SIZE_BUTTON: height = 24; break;
	    case GTK_ICON_SIZE_DIALOG: height = 48; break;
	    default:
		assert(0);
		height = ~0;
	}

	fn = g_strdup_printf("%s_%u.png", name, height);
	pix_image = GTK_IMAGE(create_pixmap(GTK_WIDGET(image), fn));
	g_free(fn);

	pixbuf = gtk_image_get_pixbuf(pix_image);
	gtk_image_set_from_pixbuf(image, pixbuf);

	gtk_widget_destroy(GTK_WIDGET(pix_image));
    }
}

static void
_install_set_action(struct install_data *idp, enum install_action ia)
{
    if (idp->id_curact && idp->id_curact->ia_action == ia)
	return;

    idp->id_curact = _install_action_find(idp, ia);

    _install_image_set_pixmap(idp->id_action_tool_img,
	    idp->id_curact->ia_pixmap,
	    GTK_ICON_SIZE_BUTTON);
    gtk_label_set_text_with_mnemonic(idp->id_action_tool_lbl,
	    idp->id_curact->ia_label);
}

static const char *
_install_port_path(struct port_desc *portdp)
{
    if (portdp->pd_origin[0] == '/')
	return portdp->pd_origin;
    else
    {
	static char path[MAXPATHLEN];

	g_snprintf(path, sizeof(path), "%s/%s",
		PORTS_BASE,
		portdp->pd_origin);
	return path;
    }
}

static gboolean
_install_file_test(const gchar *path, GFileTest gft)
{
#ifdef DEBUG_INSTALL
    warnx("checking for %s", path);
#endif /* DEBUG_INSTALL */
    return g_file_test(path, gft);
}

static void
_install_action_clicked(GtkWidget *widget, struct install_data *idp)
{
    enum install_action	ia =
	(enum install_action)g_object_get_data(G_OBJECT(widget), INSTACT_KEY);

    portlist_action(idp->id_pldatap, ia);
}

static void
_install_action_fetch_end(struct install_data *idp)
{
    gtk_statusbar_pop(idp->id_fetch_sbar, 0);
    gtk_progress_bar_set_text(idp->id_action_prog, "");
    idp->id_fetching = FALSE;
    *(idp->id_fetch_fname) = '\0';
    idp->id_fetch_size = 0;
}

static void
_on_install_action_view_log(GtkButton *button, struct install_data *idp)
{
    GtkDialog *dialog;

    dialog = GTK_DIALOG(gtk_widget_get_ancestor(GTK_WIDGET(button),
		GTK_TYPE_DIALOG));

    log_show(idp->id_ldatap);

    gtk_dialog_response(dialog, GTK_RESPONSE_CLOSE);
}

static void
_install_action_finish(struct install_data *idp, gboolean cancel)
{
    struct install_actdesc  *iadp = _install_action_find(idp,
	    idp->id_action_act);
    int			    st;

    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_prog), FALSE);
    gtk_statusbar_pop(idp->id_action_sbar, 0);
    gtk_widget_hide(GTK_WIDGET(idp->id_action_win));

    if (waitpid(idp->id_action_pid, &st, 0) == -1)
	xmk_warning(idp->id_action_win, TRUE, _("Reaping action process"));

    log_finish(idp->id_ldatap);

    gtk_input_remove(idp->id_action_iid);
    idp->id_action_iid = 0;
    mk_lines_fini(idp->id_lines);
    if (idp->id_fetching)
	_install_action_fetch_end(idp);
    close(idp->id_action_pipe);

    if (cancel)
	return;

    if (!WIFEXITED(st) || WEXITSTATUS(st))
    {
	GtkMessageDialog *dialog;
	GtkHButtonBox *bbox;
	GtkButton *button;
	GtkAlignment *align;
	GtkHBox *hbox;
	GtkImage *image;
	GtkLabel *label;

	if (WIFSIGNALED(st))
	    dialog = xmk_warning_create(idp->id_app,
		    FALSE,
		    _("%s failed: Signal %d"),
		    iadp->ia_presverb,
		    WTERMSIG(st));
	else
	    dialog = xmk_warning_create(idp->id_app,
		    FALSE,
		    _("%s failed: Error code %d"),
		    iadp->ia_presverb,
		    WEXITSTATUS(st));

	bbox = GTK_HBUTTON_BOX(GTK_DIALOG(dialog)->action_area);
	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);

	button = GTK_BUTTON(gtk_button_new());
	align = GTK_ALIGNMENT(gtk_alignment_new(0.5, 0.5, 0, 0));
	gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(align));
	hbox = GTK_HBOX(gtk_hbox_new(FALSE, 2));
	gtk_container_add(GTK_CONTAINER(align), GTK_WIDGET(hbox));
	image = GTK_IMAGE(gtk_image_new_from_stock(GTK_STOCK_INDEX,
		    GTK_ICON_SIZE_BUTTON));
	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(image), FALSE, FALSE, 0);
	gtk_widget_show(GTK_WIDGET(image));
	label = GTK_LABEL(gtk_label_new_with_mnemonic(_("View Lo_g")));
	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0);
	gtk_widget_show(GTK_WIDGET(label));
	gtk_box_pack_start_defaults(GTK_BOX(bbox), GTK_WIDGET(button));
	gtk_widget_show(GTK_WIDGET(hbox));
	gtk_widget_show(GTK_WIDGET(align));
	g_signal_connect((gpointer)button,
		"clicked",
		G_CALLBACK(_on_install_action_view_log),
		idp);
	gtk_widget_show(GTK_WIDGET(button));

	gtk_window_present(GTK_WINDOW(dialog));
    }
    else
    {
	refresh_db(idp->id_rdatap);

	xmk_notify(idp->id_app, _("%s successfully %s"),
		idp->id_action_port->pd_origin,
		iadp->ia_pastverb);

	install_enable(idp, idp->id_action_port);
    }
}

static void
_install_action_input_line(struct install_data *idp, const char *line)
{
    static const char	*prfxs[] = LINE_PRFXS;
    size_t		ll = strlen(line);
    GtkTextIter		iter;
    u_int		i;

#ifdef DEBUG
    {
	struct install_actdesc	*iadp =
	    _install_action_find(idp, idp->id_action_act);

	warnx("%s: %s", iadp->ia_target, line);
    }
#endif /* DEBUG */

    gtk_text_buffer_get_end_iter(idp->id_details_buf, &iter);

    for (i = 0; i < sizeof(prfxs) / sizeof(prfxs[0]); ++i)
    {
	size_t	plen = strlen(prfxs[i]);

	if (!strncmp(line, prfxs[i], plen))
	{
	    while (isspace(line[plen]))
		++plen;

	    gtk_statusbar_pop(idp->id_action_sbar, 0);
	    gtk_statusbar_push(idp->id_action_sbar, 0, line + plen);
	    gtk_text_buffer_insert_with_tags_by_name(idp->id_details_buf,
		    &iter,
		    line,
		    ll,
		    "emphasis",
		    NULL);
	    break;
	}
    }
    if (i == sizeof(prfxs) / sizeof(prfxs[0]))
    {
	char	outfn[1024];	/* XXX: should be PATH_MAX */
	off_t	bytes;

	if (sscanf(line, "fetch-wrapper: output file is %1024s", outfn) == 1)
	{
	    char    *cp;

	    if ((cp = strrchr(outfn, '/')))
		++cp;
	    else
		cp = outfn;
	    strlcpy(idp->id_fetch_fname, outfn, sizeof(idp->id_fetch_fname));
	}
	else if (sscanf(line, "remote size / mtime: %qu ", &bytes) == 1)
	    idp->id_fetch_size = bytes;
	else if (sscanf(line, "%qu bytes transferred in ", &bytes) == 1)
	{
	    if (idp->id_fetching)
		gtk_statusbar_pop(idp->id_fetch_sbar, 0);
	    else
		idp->id_fetching = TRUE;
	    gtk_statusbar_push(idp->id_fetch_sbar, 0, line);

	    if (idp->id_fetch_size)
	    {
		double	pcnt = (double)bytes / idp->id_fetch_size;
		char	pcnt_txt[100 + 1];

		gtk_progress_bar_set_fraction(idp->id_action_prog, pcnt);
		if (*(idp->id_fetch_fname))
		    snprintf(pcnt_txt, sizeof(pcnt_txt), "%s -- %d%%",
			    idp->id_fetch_fname,
			    (int)(pcnt * 100));
		else
		    snprintf(pcnt_txt, sizeof(pcnt_txt), "%d%%",
			    (int)(pcnt * 100));
		gtk_progress_bar_set_text(idp->id_action_prog, pcnt_txt);
	    }
	}
	else if (idp->id_fetching)
	    _install_action_fetch_end(idp);

	gtk_text_buffer_insert(idp->id_details_buf, &iter, line, ll);
    }

    gtk_text_buffer_insert(idp->id_details_buf, &iter, "\n", 1);
    gtk_text_view_scroll_to_iter(idp->id_details_text,
	    &iter,
	    0,
	    TRUE,
	    0, 0);

    log_report(idp->id_ldatap, line);

    if (!idp->id_fetching)
	gtk_progress_bar_pulse(idp->id_action_prog);
}

static void
_install_action_input(struct install_data *idp,
	gint fd,
	GdkInputCondition cond)
{
    ssize_t br;
    char    *lp;

    br = mk_lines_read(fd, idp->id_lines);

    while ((lp = mk_lines_next(idp->id_lines)))
	_install_action_input_line(idp, lp);

    if (br <= 0)
    {
	if (br == -1)
	    xmk_warning(idp->id_action_win, TRUE, _("Action input"));

	_install_action_finish(idp, FALSE);
    }
}

struct install_data *
install_init(GtkWindow *app,
	struct refresh_data *rdp,
	struct portlist_data *pldp)
{
    static struct install_data	    idata;
    static struct install_actdesc   acts[] =
    {
	{
	    INSTACT_FETCH, "fetch",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_SAVE,
	    NULL,
	    NULL
	},
	{
	    INSTACT_CHKSUM,
	    "checksum",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_APPLY,
	    NULL,
	    NULL
	},
	{
	    INSTACT_BUILD,
	    "build",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_EXECUTE,
	    NULL,
	    NULL
	},
	{INSTACT_SEP, NULL, NULL, NULL, NULL, NULL, NULL},
	{
	    INSTACT_INSTALL,
	    "install",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_CONVERT,
	    NULL,
	    NULL
	},
	{
	    INSTACT_UPGRADE,
	    "upgrade",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_GOTO_TOP,
	    NULL,
	    NULL
	},
	{
	    INSTACT_UNINST,
	    "deinstall",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_DELETE,
	    NULL,
	    NULL
	},
	{INSTACT_SEP, NULL, NULL, NULL, NULL, NULL, NULL},
	{
	    INSTACT_CLEAN,
	    "clean",
	    NULL, NULL, NULL, NULL,
	    GTK_STOCK_CLEAR,
	    NULL,
	    NULL
	},
	{
	    INSTACT_PACKAGE,
	    "package-noinstall",
	    NULL, NULL, NULL, NULL,
	    "pkg",
	    NULL,
	    NULL
	},
	{INSTACT_NONE, NULL, NULL, NULL, NULL, NULL, NULL}
    };
    u_int			    i;

    idata.id_rdatap = rdp;
    idata.id_pldatap = pldp;
    idata.id_ldatap = log_init();
    idata.id_app = app;

    idata.id_curact = NULL;

    idata.id_mitem = GTK_MENU_ITEM(lookup_widget(GTK_WIDGET(app),
		"action_mitem"));
    idata.id_menu = GTK_MENU(gtk_menu_new());
    gtk_menu_item_set_submenu(idata.id_mitem, GTK_WIDGET(idata.id_menu));
    idata.id_popup = GTK_MENU(gtk_menu_new());
    gtk_widget_show(GTK_WIDGET(idata.id_popup));

    idata.id_action_win = GTK_WINDOW(create_action_win());
    gtk_window_set_transient_for(idata.id_action_win, app);
    gtk_window_set_position(idata.id_action_win,
	    GTK_WIN_POS_CENTER_ON_PARENT);

    idata.id_action_img =
	GTK_IMAGE(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_img"));
    idata.id_action_lbl =
	GTK_LABEL(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_lbl"));
    idata.id_action_prog =
	GTK_PROGRESS_BAR(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_prog"));
    idata.id_action_sbar =
	GTK_STATUSBAR(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_sbar"));
    idata.id_fetch_sbar =
	GTK_STATUSBAR(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "fetch_sbar"));
    idata.id_action_btn =
	GTK_BUTTON(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_btn"));
    idata.id_action_btn_img =
	GTK_IMAGE(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_btn_img"));
    idata.id_action_btn_lbl =
	GTK_LABEL(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "action_btn_lbl"));

    idata.id_details_btn =
	GTK_TOGGLE_BUTTON(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "details_btn"));
    idata.id_details_sw =
	GTK_SCROLLED_WINDOW(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "details_sw"));
    gtk_widget_hide(GTK_WIDGET(idata.id_details_sw));

    idata.id_details_text =
	GTK_TEXT_VIEW(lookup_widget(GTK_WIDGET(idata.id_action_win),
		    "details_text"));
    idata.id_details_buf = gtk_text_view_get_buffer(idata.id_details_text);
    gtk_text_buffer_create_tag(idata.id_details_buf, "emphasis",
	    "weight", PANGO_WEIGHT_BOLD,
	    NULL);

    idata.id_action_tool = GTK_BUTTON(lookup_widget(GTK_WIDGET(app),
		"action_tool"));
    idata.id_action_tool_img = GTK_IMAGE(lookup_widget(GTK_WIDGET(app),
		"action_tool_img"));
    idata.id_action_tool_lbl = GTK_LABEL(lookup_widget(GTK_WIDGET(app),
		"action_tool_lbl"));
    idata.id_action_ctool = GTK_BUTTON(lookup_widget(GTK_WIDGET(app),
		"action_ctool"));

    idata.id_package_dlg = GTK_FILE_CHOOSER_DIALOG(create_package_dlg());
    gtk_window_set_transient_for(GTK_WINDOW(idata.id_package_dlg),
	    idata.id_action_win);

    idata.id_actions = acts;
    for (i = 0; acts[i].ia_action != INSTACT_NONE; ++i)
    {
	switch (acts[i].ia_action)
	{
	    case INSTACT_FETCH:
		acts[i].ia_presverb = _("Fetch");
		acts[i].ia_progverb = _("Fetching");
		acts[i].ia_pastverb = _("Fetched");
		acts[i].ia_label = _("_Fetch");
		break;

	    case INSTACT_CHKSUM:
		acts[i].ia_presverb = _("Checksum");
		acts[i].ia_progverb = _("Checking sums");
		acts[i].ia_pastverb = _("Checked sums");
		acts[i].ia_label = _("_Checksum");
		break;

	    case INSTACT_BUILD:
		acts[i].ia_presverb = _("Build");
		acts[i].ia_progverb = _("Building");
		acts[i].ia_pastverb = _("Built");
		acts[i].ia_label = _("_Build");
		break;

	    case INSTACT_INSTALL:
		acts[i].ia_presverb = _("Install");
		acts[i].ia_progverb = _("Installing");
		acts[i].ia_pastverb = _("Installed");
		acts[i].ia_label = _("_Install");
		break;

	    case INSTACT_UPGRADE:
		acts[i].ia_presverb = _("Upgrade");
		acts[i].ia_progverb = _("Upgrading");
		acts[i].ia_pastverb = _("Upgraded");
		acts[i].ia_label = _("Up_grade");
		break;

	    case INSTACT_UNINST:
		acts[i].ia_presverb = _("Uninstall");
		acts[i].ia_progverb = _("Uninstalling");
		acts[i].ia_pastverb = _("Uninstalled");
		acts[i].ia_label = _("_Uninstall");
		break;

	    case INSTACT_CLEAN:
		acts[i].ia_presverb = _("Clean");
		acts[i].ia_progverb = _("Cleaning");
		acts[i].ia_pastverb = _("Cleaned");
		acts[i].ia_label = _("C_lean");
		break;

	    case INSTACT_PACKAGE:
		acts[i].ia_presverb = _("Package");
		acts[i].ia_progverb = _("Packaging");
		acts[i].ia_pastverb = _("Packaged");
		acts[i].ia_label = _("_Package");
		break;

	    case INSTACT_CURRENT:
	    case INSTACT_SEP:
	    case INSTACT_NONE:
		break;
	}

	if (acts[i].ia_action == INSTACT_SEP)
	{
	    GtkSeparatorMenuItem    *sep_mitem =
		GTK_SEPARATOR_MENU_ITEM(gtk_separator_menu_item_new());

	    gtk_container_add(GTK_CONTAINER(idata.id_menu),
		    GTK_WIDGET(sep_mitem));
	    gtk_widget_show(GTK_WIDGET(sep_mitem));
	}
	else
	{
	    GtkImage    *image;

	    acts[i].ia_mitem = GTK_IMAGE_MENU_ITEM(
		    gtk_image_menu_item_new_with_mnemonic(acts[i].ia_label));

	    image = GTK_IMAGE(gtk_image_new());
	    _install_image_set_pixmap(image,
		    acts[i].ia_pixmap,
		    GTK_ICON_SIZE_MENU);
	    gtk_image_menu_item_set_image(acts[i].ia_mitem,
		    GTK_WIDGET(image));
	    gtk_widget_show(GTK_WIDGET(image));

	    gtk_container_add(GTK_CONTAINER(idata.id_menu),
		    GTK_WIDGET(acts[i].ia_mitem));
	    g_object_set_data(G_OBJECT(acts[i].ia_mitem),
		    INSTACT_KEY,
		    (gpointer)acts[i].ia_action);
	    g_signal_connect((gpointer)acts[i].ia_mitem,
		    "activate",
		    G_CALLBACK(_install_action_clicked),
		    &idata);
	    gtk_widget_show(GTK_WIDGET(acts[i].ia_mitem));

	    acts[i].ia_tool = GTK_IMAGE_MENU_ITEM(
		    gtk_image_menu_item_new_with_mnemonic(acts[i].ia_label));

	    image = GTK_IMAGE(gtk_image_new());
	    _install_image_set_pixmap(image,
		    acts[i].ia_pixmap,
		    GTK_ICON_SIZE_BUTTON);
	    gtk_image_menu_item_set_image(acts[i].ia_tool, GTK_WIDGET(image));
	    gtk_widget_show(GTK_WIDGET(image));

	    gtk_container_add(GTK_CONTAINER(idata.id_popup),
		    GTK_WIDGET(acts[i].ia_tool));
	    g_object_set_data(G_OBJECT(acts[i].ia_tool),
		    INSTACT_KEY,
		    (gpointer)acts[i].ia_action);
	    g_signal_connect((gpointer)acts[i].ia_tool,
		    "activate",
		    G_CALLBACK(_install_action_clicked),
		    &idata);
	    gtk_widget_show(GTK_WIDGET(acts[i].ia_tool));
	}
    }

    _install_set_action(&idata, INSTACT_INSTALL);

    gtk_widget_set_sensitive(GTK_WIDGET(idata.id_action_ctool), FALSE);
    xmk_combo_tool_bind(idata.id_action_tool,
	    idata.id_action_ctool,
	    idata.id_popup);

    idata.id_action_iid = 0;
    idata.id_lines = NULL;
    /* XXX: code duplication with _install_action_fetch_end() */
    idata.id_fetching = FALSE;
    *(idata.id_fetch_fname) = '\0';
    idata.id_fetch_size = 0;

    return (idatap = &idata);
}

void
install_fini(struct install_data *idp)
{
    log_fini(idp->id_ldatap);
}

void
install_enable(struct install_data *idp, struct port_desc *portdp)
{
    u_int   i;

    if (portdp->pd_state == PSTATE_INST)
	_install_set_action(idp, INSTACT_UNINST);
    else if (portdp->pd_state == PSTATE_STALE)
	_install_set_action(idp, INSTACT_UPGRADE);
    else
	_install_set_action(idp, INSTACT_INSTALL);

    for (i = 0; idp->id_actions[i].ia_action != INSTACT_NONE; ++i)
    {
	gboolean    enable;
	gchar	    path[MAXPATHLEN];
	const gchar *ppath;

	switch (idp->id_actions[i].ia_action)
	{
	    case INSTACT_FETCH:
	    case INSTACT_CHKSUM:
		/* Always possible to fetch or checksum */
		continue;

	    case INSTACT_BUILD:
		/* XXX: should use make -v BUILD_COOKIE */
		ppath = _install_port_path(portdp),
		g_snprintf(path, sizeof(path), "%s/work/.build_done.%s-%s",
			ppath,
			portdp->pd_name,
			portdp->pd_version);
		if (_install_file_test(path, G_FILE_TEST_IS_REGULAR) == TRUE)
		    enable = TRUE;
		else
		{
		    gchar   prefix[MAXPATHLEN],
			    *cp;

		    g_strlcpy(prefix, portdp->pd_prefix, sizeof(prefix));
		    cp = prefix;
		    while ((cp = strchr(cp, '/')))
			*cp++ = '_';
		    g_snprintf(path, sizeof(path),
			    "%s/work/.build_done.%s-%s.%s",
			    ppath,
			    portdp->pd_name,
			    portdp->pd_version,
			    prefix);
		    enable =
			(_install_file_test(path, G_FILE_TEST_IS_REGULAR)) ?
			FALSE : TRUE;
		}
		break;

	    case INSTACT_INSTALL:
		enable = (portdp->pd_state == PSTATE_UNINST) ? TRUE : FALSE;
		break;

	    case INSTACT_UPGRADE:
		enable = (portdp->pd_state == PSTATE_STALE) ? TRUE : FALSE;
		break;

	    case INSTACT_UNINST:
		enable = (portdp->pd_state != PSTATE_UNINST) ? TRUE : FALSE;
		break;

	    case INSTACT_CLEAN:
		/* FIXME: should do make -V WRKDIR */
		g_snprintf(path, sizeof(path),
			"%s/work",
			_install_port_path(portdp));
		enable = _install_file_test(path, G_FILE_TEST_IS_DIR);
		break;

	    case INSTACT_PACKAGE:
		enable = (portdp->pd_state == PSTATE_INST) ? TRUE : FALSE;
		break;

	    case INSTACT_SEP:
		continue;

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

	gtk_widget_set_sensitive(GTK_WIDGET(idp->id_actions[i].ia_mitem),
		enable);
	((enable == TRUE) ? gtk_widget_show :
	 gtk_widget_hide)(GTK_WIDGET(idp->id_actions[i].ia_tool));
    }

    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_mitem), TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_tool), TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_ctool), TRUE);
}

void
install_disable(struct install_data *idp)
{
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_ctool), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_tool), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_mitem), FALSE);
}

void
install_action(struct install_data *idp,
	struct port_desc *pdescp,
	enum install_action ia)
{
    struct install_actdesc  *iadp = _install_action_find(idp, ia);
    gchar		    *desc;

    idp->id_action_act = ia;
    idp->id_action_port = pdescp;

    gtk_window_set_title(idp->id_action_win, iadp->ia_presverb);

    _install_image_set_pixmap(idp->id_action_img,
	    iadp->ia_pixmap,
	    GTK_ICON_SIZE_DIALOG);

    desc = g_strdup_printf(_("<big><b>%s for %s</b></big>"),
	    iadp->ia_progverb,
	    pdescp->pd_origin);
    gtk_label_set_markup(idp->id_action_lbl, desc);
    g_free(desc);

    _install_image_set_pixmap(idp->id_action_btn_img,
	    iadp->ia_pixmap,
	    GTK_ICON_SIZE_BUTTON);

    gtk_label_set_text_with_mnemonic(idp->id_action_btn_lbl, iadp->ia_label);

    gtk_statusbar_push(idp->id_action_sbar, 0, _("Ready"));
    gtk_progress_bar_set_fraction(idp->id_action_prog, 0);

    install_details_show(idp, FALSE);

    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_btn), TRUE);
    gtk_window_set_focus(idp->id_action_win, GTK_WIDGET(idp->id_action_btn));

    gtk_window_present(idp->id_action_win);
}

void
install_menu_popup(struct install_data *idp, GdkEventButton *event)
{
    gtk_menu_popup(idp->id_menu,
	    NULL,
	    NULL,
	    NULL,
	    NULL,
	    event->button,
	    event->time);
}

void
install_details_show(struct install_data *idp, gboolean show)
{
#ifdef DEBUG
    warnx("%s(%d), wflags = %#x, state = %#x",
	    __func__,
	    show,
	    GTK_WIDGET(idp->id_details_sw)->private_flags,
	    GTK_WIDGET_STATE(idp->id_details_sw));
#endif /* DEBUG */

    ((show) ? gtk_widget_show : gtk_widget_hide)
	(GTK_WIDGET(idp->id_details_sw));

    gtk_toggle_button_set_active(idp->id_details_btn, show);
}

void
install_action_run(struct install_data *idp)
{
    struct install_actdesc  *iadp = _install_action_find(idp,
				    idp->id_action_act);
    struct port_desc	    *pdescp = idp->id_action_port;
    int			    fds[2];
    u_int		    ac = 0;
    char		    *av[32],
			    *desc;
    gchar		    *pkg = NULL,
			    *target = NULL;
    const char		    *path;

    if (iadp->ia_action == INSTACT_UPGRADE)
    {
	av[ac++] = "portupgrade";
	av[ac++] = "-R";
	av[ac++] = "-M";
	av[ac++] = "SU_CMD=" SU_CMD;
	av[ac++] = "-M";
	av[ac++] = "FETCH_CMD=" FETCH_CMD;
	av[ac++] = "-s";
	av[ac++] = "-S";
	av[ac++] = SU_CMD " '%s'";
	av[ac++] = "-o";
	av[ac++] = pdescp->pd_origin;
	av[ac++] = pdescp->pd_name;
	av[ac++] = NULL;
    }
    else
    {
	av[ac++] = "make";
	av[ac++] = "SU_CMD=" SU_CMD;
	av[ac++] = "FETCH_CMD=" FETCH_CMD;

	if (iadp->ia_action == INSTACT_PACKAGE)
	{
	    gchar *fn;

	    fn = g_strdup_printf("%s-%s" PKG_SUFX,
		    pdescp->pd_name,
		    pdescp->pd_version);
	    gtk_file_chooser_set_current_name(
		    GTK_FILE_CHOOSER(idp->id_package_dlg),
		    fn);

	    if (gtk_dialog_run(GTK_DIALOG(idp->id_package_dlg)) !=
		    GTK_RESPONSE_ACCEPT)
		return;

	    g_free(fn);

	    fn = gtk_file_chooser_get_filename(
			GTK_FILE_CHOOSER(idp->id_package_dlg));
	    pkg = g_strdup_printf("PKGFILE=%s", fn);
	    g_free(fn);
	    av[ac++] = pkg;
	}

	av[ac++] = target = g_strdup(iadp->ia_target);
	av[ac++] = NULL;
    }
    assert(ac <= sizeof(av) / sizeof(av[0]));

    path = _install_port_path(pdescp);
    idp->id_action_pid = mk_spawn(path, fds, av, &desc);

    if (iadp->ia_action == INSTACT_PACKAGE)
	g_free(pkg);

    if (iadp->ia_action != INSTACT_UPGRADE)
	g_free(target);

    if (idp->id_action_pid == -1)
    {
	xmk_warning(idp->id_action_win, TRUE, _("Action error: %s"), desc);
	return;
    }

    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_prog), TRUE);
    gtk_statusbar_push(idp->id_action_sbar, 0, iadp->ia_progverb);
    gtk_widget_set_sensitive(GTK_WIDGET(idp->id_action_btn), FALSE);

    log_start(idp->id_ldatap, path, iadp->ia_progverb);

    idp->id_lines = mk_lines_init();
    idp->id_action_pipe = fds[0];
    idp->id_action_iid = gtk_input_add_full(idp->id_action_pipe,
	    GDK_INPUT_READ,
	    (GdkInputFunction)_install_action_input,
	    NULL,
	    idp,
	    NULL);
}

void
install_action_cancel(struct install_data *idp)
{
    if (idp->id_action_iid)
    {
	if (kill(idp->id_action_pid, SIGTERM) == -1)
	    xmk_warning(idp->id_action_win, TRUE,
		    _("Killing action process"));

	_install_action_finish(idp, TRUE);
    }

    gtk_widget_hide(GTK_WIDGET(idp->id_action_win));
}

