/***************************************************************************
                          connect-dialog.c
                          ----------------
    begin                : 09 August 2004
    copyright            : (C) 2004 by Tim-Philipp Müller
    email                : t.i.m at orange dot net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#undef GTK_DISABLE_DEPRECATED /* for GtkOptionMenu */

#include "global.h"

#include "connect-dialog.h"
#include "core-conn.h"
#include "misc.h"
#include "misc_gtk.h"
#include "options.h"
#include "status_page.h"
#include "toolbar.h" /* for DOCS_URL */

#ifdef G_OS_UNIX
# include <sys/time.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <signal.h>
# include <unistd.h>
#endif

#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <gtk/gtk.h>

typedef struct _GuiConnectDialog GuiConnectDialog;
typedef struct _GuiLocalCore     GuiLocalCore;

struct _GuiLocalCore
{
	gchar   *cookiefn;
	gchar   *cookie;
	guint    adminport;
	guint64  corepid;
	time_t   modtime;
};

struct _GuiConnectDialog
{
	GtkWidget    *mainwindow;
	GtkWidget    *dialog;

	GtkWidget    *align_start_core;
	GtkWidget    *align_remote_core;

	GtkWidget    *optmenu_local_core_start;
	
	GtkWidget    *rb_start_core;
	GtkWidget    *rb_local_core;
	GtkWidget    *rb_remote_core;

	GtkWidget    *button_ok;
	GtkWidget    *label_no_cores_found;
	
	GtkWidget    *entry_host;
	GtkWidget    *entry_port;
	GtkWidget    *entry_user;
	GtkWidget    *entry_pass;

	GtkWidget    *menu_start_core;  /* for GtkOptionMenu                      */
	GHashTable   *core_bin_items;   /* core path => GtkMenuItem               */
	GList        *core_bin_list;    /* core path strings, owned by hash table */
	gchar        *active_core_path; /* core to start                          */
	
	GuiLocalCore *local_core;

	guint         num_port;
	const gchar  *str_host;
	const gchar  *str_user;
	const gchar  *str_pass;

	guint         timer_id;         /* checks for running local cores */
	guint         timer_id_initial; /* used to show dialog first time */
};

static void        cdialog_free_cd_struct (GuiConnectDialog *cd);

static void        cdialog_start_local_core_checking (GuiConnectDialog *cd);

static void        cdialog_stop_local_core_checking (GuiConnectDialog *cd);

static gboolean    cdialog_cookie_file_permissions_ok (const gchar *cookiefn, time_t *p_modtime);

static void        cdialog_cookie_read (const gchar *dir, const gchar *entry, GList **p_core_list);

static void        cdialog_scan_dir_for_cookies (const gchar *dir, GList **p_core_list);

static gboolean    cdialog_show_delayed (GuiConnectDialog *cd);

static void        cdialog_entries_changed (GuiConnectDialog *cd, GtkEntry *e);

static void        cdialog_scan_path_for_core_binaries (GuiConnectDialog *cd);


/******************************************************************************
 *
 *   cdialog_free_cd_struct
 *
 ******************************************************************************/

static void
cdialog_free_cd_struct (GuiConnectDialog *cd)
{
	if (cd->timer_id_initial > 0)
		g_source_remove (cd->timer_id_initial);
	
	cdialog_stop_local_core_checking (cd);
	
	if (cd->core_bin_items)
		g_hash_table_destroy (cd->core_bin_items);

	g_list_free (cd->core_bin_list);

	memset (cd, 0xff, sizeof(GuiConnectDialog));
	
	g_free(cd);
}


/******************************************************************************
 *
 *   cdialog_free_local_core
 *
 ******************************************************************************/

static void
cdialog_free_local_core (GuiLocalCore *lc)
{
	g_free (lc->cookie);
	g_free (lc->cookiefn);
	memset (lc, 0xff, sizeof (GuiLocalCore));
	g_free (lc);
}


/******************************************************************************
 *
 *  cdialog_show
 *
 ******************************************************************************/

static void
cdialog_show (GuiConnectDialog *cd)
{
	gtk_widget_show (cd->dialog);
	
	cdialog_start_local_core_checking (cd);
}

/******************************************************************************
 *
 *  cdialog_hide
 *
 ******************************************************************************/

static void
cdialog_hide (GuiConnectDialog *cd)
{
	gtk_widget_hide (cd->dialog);
	
	cdialog_stop_local_core_checking (cd);

	if (cd->timer_id_initial)
		g_source_remove (cd->timer_id_initial);
}


/******************************************************************************
 *
 *  cdialog_core_conn_status_cb
 *
 ******************************************************************************/

static void
cdialog_core_conn_status_cb (GuiConnectDialog *cd, guint status, GuiCoreConn *conn)
{
	static guint  commcounter; /* 0 */  /* how often we had status 'communicating' */

	switch (status)
	{
		case CONN_STATUS_CLOSED_TIMEOUT:
		case CONN_STATUS_CLOSED_TIMEOUT_NO_DATA:
		case CONN_STATUS_CLOSED_UNEXPECTEDLY:
			/* only show connect dialog if we have
			 *  not been connected to a core before.
			 *  Otherwise reconnect dialog will kick in */
			if (commcounter == 0)
				cdialog_show (cd);
			break;

		case CONN_STATUS_CLOSED_DELIBERATELY:
			cdialog_show (cd);
			break;

		case CONN_STATUS_COMMUNICATING:
			++commcounter;
			/* fallthrough! */

		default:
			cdialog_hide (cd);
			break;
	}
}

/******************************************************************************
 *
 *   cdialog_update_widgets_visibility
 *
 ******************************************************************************/

static void
cdialog_update_widgets_visibility (GuiConnectDialog *cd)
{
	/* local core already running? */
	if (cd->local_core)
	{
		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_start_core)))
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cd->rb_local_core), TRUE);

		gtk_widget_hide (cd->rb_start_core);
		gtk_widget_hide (cd->align_start_core);
		gtk_widget_show (cd->rb_local_core);
	}
	else
	{
		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_local_core)))
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cd->rb_start_core), TRUE);
		
		cdialog_scan_path_for_core_binaries (cd);

		gtk_widget_show (cd->rb_start_core);
		gtk_widget_show (cd->align_start_core);
		gtk_widget_hide (cd->rb_local_core);
	}

	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_remote_core)))
	{
		gtk_widget_show (cd->align_remote_core);
		cdialog_entries_changed (cd, NULL);
		gtk_widget_hide (cd->align_start_core);
		opt_set_bool (OPT_GUI_DEFAULT_REMOTE_CONNECT, TRUE);
	}
	else
	{
		gtk_widget_hide (cd->align_remote_core);
		gtk_widget_set_sensitive (cd->button_ok, TRUE);
		opt_set_bool (OPT_GUI_DEFAULT_REMOTE_CONNECT, FALSE);
	}

	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_start_core)))
	{
		/* no cores found? */
		if (g_hash_table_size (cd->core_bin_items) == 0)
		{
			gtk_widget_set_sensitive (cd->button_ok, FALSE);
			gtk_widget_show (cd->label_no_cores_found);
			gtk_widget_hide (cd->optmenu_local_core_start);
			g_free (cd->active_core_path);
			cd->active_core_path = NULL;
		}
		else
		{
			gtk_widget_set_sensitive (cd->button_ok, TRUE);
			gtk_widget_hide (cd->label_no_cores_found);
			gtk_widget_show (cd->optmenu_local_core_start);
		}
	}
}

/******************************************************************************
 *
 *   cdialog_find_default_cookie
 *
 ******************************************************************************/

static GuiLocalCore *
cdialog_find_default_core_cookie (void)
{
	GuiLocalCore *deflc;
	const gchar  *defcfn;
	GList        *l, *core_list = NULL;
	
	cdialog_scan_dir_for_cookies (g_get_tmp_dir(), &core_list);
	
	if (core_list == NULL)
		return NULL;
	

	deflc = NULL;
		
	/* if we have a cookie saved and it exists in the list,
	 *  prefer that to the first one in the list */
	if ((defcfn = opt_get_str (OPT_GUI_DEFAULT_CORE_COOKIE)))
	{
		for (l = core_list;  l && !deflc;  l = l->next)
		{
			if (g_str_equal (l->data, defcfn))
				deflc = (GuiLocalCore*) l->data;
		}
	}

	if (deflc == NULL)
		deflc = (GuiLocalCore*) core_list->data;
		
	for (l = core_list->next;  l;  l = l->next)
	{
		if (l->data != deflc)
			cdialog_free_local_core ((GuiLocalCore*) l->data);
	}
		
	g_list_free (core_list);
	
	return deflc;
}


/******************************************************************************
 *
 *   connect_dialog_local_core_running
 *
 ******************************************************************************/

gboolean   
connect_dialog_local_core_running (guint *p_port, gchar **p_cookie)
{
	GuiLocalCore *deflc;

	deflc = cdialog_find_default_core_cookie ();

	if (deflc == NULL)
		return FALSE;

	if (p_port)
		*p_port = deflc->adminport;

	if (p_cookie)
	{
		*p_cookie = deflc->cookie;
		deflc->cookie = NULL;
	}
	
	cdialog_free_local_core	(deflc);

	return TRUE;
}

/******************************************************************************
 *
 *   cdialog_core_check_cb
 *
 *   TODO: show _all_ detected local cores? But how to do 
 *         that UI wise without ending up with a messy UI?
 *
 ******************************************************************************/

static gboolean
cdialog_core_check_cb (GuiConnectDialog *cd)
{
	GuiLocalCore *lc;

	lc = cdialog_find_default_core_cookie ();
		
	if (lc == NULL)
	{
		if (cd->local_core)
		{
			cdialog_free_local_core (cd->local_core);
			cd->local_core = NULL;
		}
	}
	else
	{
		cd->local_core = lc;
	}

	cdialog_update_widgets_visibility (cd);
	
	return TRUE; /* call again until removed with g_source_remove() */
}

/******************************************************************************
 *
 *   cdialog_start_local_core_checking
 *
 ******************************************************************************/

static void
cdialog_start_local_core_checking (GuiConnectDialog *cd)
{
	if (cd->timer_id > 0)
		return;
	
	if (cdialog_core_check_cb (cd))
		cd->timer_id = g_timeout_add (2000, (GSourceFunc) cdialog_core_check_cb, cd);
}


/******************************************************************************
 *
 *   cdialog_stop_local_core_checking
 *
 ******************************************************************************/

static void
cdialog_stop_local_core_checking (GuiConnectDialog *cd)
{
	if (cd->timer_id == 0)
		return;

	g_source_remove (cd->timer_id);
	cd->timer_id = 0;
}


/***************************************************************************
 *
 *   cdialog_connect_to_local_core
 *
 ***************************************************************************/

static void
cdialog_connect_to_local_core (GuiConnectDialog *cd)
{
	g_return_if_fail (cd->local_core != NULL);

	status_message_blue (_("Connecting to locally running core (%s)\n"), cd->local_core->cookiefn);

	g_return_if_fail (cd->local_core->cookie != NULL);
	g_return_if_fail (cd->local_core->adminport > 0);
	
	(void) gui_core_conn_connect (core, "127.0.0.1", cd->local_core->adminport, 
	                              "local_cookie", cd->local_core->cookie);

	opt_set_str (OPT_GUI_DEFAULT_CORE_COOKIE, cd->local_core->cookiefn);
}

/***************************************************************************
 *
 *   cdialog_connect_to_remote_core
 *
 ***************************************************************************/

static void
cdialog_connect_to_remote_core (GuiConnectDialog *cd)
{
	status_message_blue (_("Preparing to connect to %s:%u\n"), cd->str_host, cd->num_port);

	(void) gui_core_conn_connect (core, cd->str_host, cd->num_port, 
	                              cd->str_user, cd->str_pass);

	opt_set_str (OPT_GUI_DEFAULT_CORE_HOST, cd->str_host);
	opt_set_str (OPT_GUI_DEFAULT_CORE_USER, cd->str_user);
	opt_set_str (OPT_GUI_DEFAULT_CORE_PASS, cd->str_pass);
	opt_set_int (OPT_GUI_DEFAULT_CORE_PORT, cd->num_port);
}


/***************************************************************************
 *
 *   cdialog_start_local_core
 *
 ***************************************************************************/

static void
cdialog_start_local_core (GuiConnectDialog *cd)
{
	GError *err = NULL;

	g_return_if_fail (cd->active_core_path != NULL);

	if (!connect_dialog_spawn_core (cd->active_core_path, &err))
	{
		g_print ("Error spawning core: %s\n", err->message);
		status_warning ("Error spawning core: %s\n", err->message);
		g_error_free (err);
	}
	else
	{
		status_message_blue (_("Started core.\n"));
		opt_set_str (OPT_GUI_DEFAULT_CORE_BINARY_PATH, cd->active_core_path);
	}
}

/***************************************************************************
 *
 *   cdialog_response_ok
 *
 ***************************************************************************/

static void
cdialog_response_ok (GuiConnectDialog *cd)
{
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_local_core)))
	{
		cdialog_connect_to_local_core (cd);
	}
	else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->rb_start_core)))
	{
		cdialog_start_local_core (cd);
	}
	else
	{
		cdialog_connect_to_remote_core (cd);
	}
}

/***************************************************************************
 *
 *   cdialog_response
 *
 ***************************************************************************/

static void
cdialog_response (GuiConnectDialog *cd, gint response, GtkDialog *d)
{
	switch (response)
	{
		case GTK_RESPONSE_OK:
			cdialog_response_ok (cd);
			break;
		
		case GTK_RESPONSE_HELP:
			invoke_browser_with_url (DOCS_URL);
			break;

		case GTK_RESPONSE_DELETE_EVENT:
			break;
		
		case GTK_RESPONSE_CANCEL: /* = Quit */
			gtk_main_quit ();
			break;

		default:
			g_warning ("unhandled response %d at %s\n", response, G_STRLOC);
			break;
	}
}

/***************************************************************************
 *
 *   cdialog_radiobutton_toggled
 *
 ***************************************************************************/

static void
cdialog_radiobutton_toggled (GuiConnectDialog *cd, GtkToggleButton *rb)
{
	if (!gtk_toggle_button_get_active (rb))
		return;

	cdialog_update_widgets_visibility (cd);
}

/***************************************************************************
 *
 *   cdialog_delete_event_cb
 *
 ***************************************************************************/

static gboolean
cdialog_delete_event_cb (GuiConnectDialog *cd, GtkWidget *w)
{
	return TRUE; /* do not delete dialog */
}


/***************************************************************************
 *
 *   cdialog_start_core_item_activated
 *
 ***************************************************************************/

static void
cdialog_start_core_item_activated (GuiConnectDialog *cd, GObject *mi)
{
	g_free (cd->active_core_path);
	cd->active_core_path = g_strdup (g_object_get_data (mi, "core-bin-path"));
}

/***************************************************************************
 *
 *   cdialog_entries_changed
 *
 ***************************************************************************/

static void
cdialog_entries_changed (GuiConnectDialog *cd, GtkEntry *e)
{
	gtk_widget_set_sensitive (cd->button_ok, FALSE);
	
	cd->str_host = gtk_entry_get_text (GTK_ENTRY (cd->entry_host));
	cd->num_port = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (cd->entry_port));
	cd->str_user = gtk_entry_get_text (GTK_ENTRY (cd->entry_user));
	cd->str_pass = gtk_entry_get_text (GTK_ENTRY (cd->entry_pass));

	if (cd->str_host && *(cd->str_host)
	 && cd->str_user && *(cd->str_user)
	 && cd->str_pass && *(cd->str_pass))
		gtk_widget_set_sensitive (cd->button_ok, TRUE);
}


/***************************************************************************
 *
 *   connect_dialog_load
 *
 ***************************************************************************/

static void
connect_dialog_load (GuiConnectDialog *cd)
{
	const gchar *s;
	guint        i;

	if (!misc_gtk_load_interface ("ui-connect-dialog.glade",  FALSE,
	                              "connect-dialog", &cd->dialog,
	                              "option-menu-local-core-start", &cd->optmenu_local_core_start,
	                              "no-cores-found-label", &cd->label_no_cores_found,
	                              "alignment-start-core", &cd->align_start_core,
	                              "alignment-remote-core", &cd->align_remote_core,
	                              "rb-start-core", &cd->rb_start_core,
	                              "rb-local-core", &cd->rb_local_core,
	                              "rb-remote-core", &cd->rb_remote_core,
	                              "button-ok", &cd->button_ok,
	                              "entry-host", &cd->entry_host,
	                              "entry-port", &cd->entry_port,
	                              "entry-user", &cd->entry_user,
	                              "entry-pass", &cd->entry_pass,
	                              NULL))
	{
		GtkWidget *md;

		gtk_widget_hide (cd->mainwindow);

		md = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR,
		                             GTK_BUTTONS_CLOSE,
		                             _("Could not load 'ui-connect-dialog.glade'.\n"
		                               "Check your ed2k-gtk-gui installation!\n"));

		gtk_dialog_run (GTK_DIALOG (md));

		gtk_widget_destroy (md);

		exit (1); /* evil, but who cares? */
	}

	cd->menu_start_core = gtk_menu_new ();
	gtk_widget_show (cd->menu_start_core);
	gtk_option_menu_set_menu (GTK_OPTION_MENU (cd->optmenu_local_core_start), cd->menu_start_core);
	cd->core_bin_items = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);

	if ((s = opt_get_str (OPT_GUI_DEFAULT_CORE_HOST)))
		gtk_entry_set_text (GTK_ENTRY (cd->entry_host), s);
	
	if ((s = opt_get_str (OPT_GUI_DEFAULT_CORE_USER)))
		gtk_entry_set_text (GTK_ENTRY (cd->entry_user), s);
	
	if ((s = opt_get_str (OPT_GUI_DEFAULT_CORE_PASS)))
		gtk_entry_set_text (GTK_ENTRY (cd->entry_pass), s);

	if ((i = opt_get_int (OPT_GUI_DEFAULT_CORE_PORT)))
		g_object_set (cd->entry_port, "value", (gdouble) i, NULL);

	g_signal_connect_swapped (cd->dialog, "delete-event",
	                          G_CALLBACK (cdialog_delete_event_cb), cd);
	
	g_signal_connect_swapped (cd->dialog, "response",
	                          G_CALLBACK (cdialog_response), cd);
	
	g_signal_connect_swapped (cd->rb_remote_core, "toggled",
	                          G_CALLBACK (cdialog_radiobutton_toggled), cd);
	
	g_signal_connect_swapped (cd->rb_start_core, "toggled",
	                          G_CALLBACK (cdialog_radiobutton_toggled), cd);
	
	g_signal_connect_swapped (cd->rb_local_core, "toggled",
	                          G_CALLBACK (cdialog_radiobutton_toggled), cd);

	g_signal_connect_swapped (cd->entry_host, "changed",
	                          G_CALLBACK (cdialog_entries_changed), cd);
	
	g_signal_connect_swapped (cd->entry_port, "changed",
	                          G_CALLBACK (cdialog_entries_changed), cd);

	g_signal_connect_swapped (cd->entry_user, "changed",
	                          G_CALLBACK (cdialog_entries_changed), cd);

	g_signal_connect_swapped (cd->entry_pass, "changed",
	                          G_CALLBACK (cdialog_entries_changed), cd);

	if (opt_get_bool (OPT_GUI_DEFAULT_REMOTE_CONNECT))
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cd->rb_remote_core), TRUE);
	
	cdialog_update_widgets_visibility (cd);
}


/******************************************************************************
 *
 *   cdialog_show_delayed
 *
 ******************************************************************************/

static gboolean
cdialog_show_delayed (GuiConnectDialog *cd)
{
	cd->timer_id_initial = 0;

	cdialog_show (cd);

	return FALSE; /* only call once */
}

/***************************************************************************
 *
 *   connect_dialog_create
 *
 ***************************************************************************/

void
connect_dialog_create (GtkWidget *mainwindow)
{
	GuiConnectDialog *cd;

	cd = g_new0 (GuiConnectDialog, 1);

	cd->mainwindow = mainwindow;

	connect_dialog_load (cd);
	cdialog_hide (cd);

	/* Make sure allocated memory is
	 *  freed when dialog is destroyed */
	g_object_set_data_full (G_OBJECT (cd->dialog), "cd-memory", 
	                        cd, (GDestroyNotify) cdialog_free_cd_struct);

	g_signal_connect_swapped (core, "core-conn-status", 
	                          G_CALLBACK (cdialog_core_conn_status_cb), 
	                          cd);

	/* Use a short timeout to make sure we don't
	 *  show the dialog unnecessarily when
	 *  using auto-connect on startup */
	cd->timer_id_initial = g_timeout_add (250, (GSourceFunc) cdialog_show_delayed, cd);
}

/***************************************************************************
 *
 *    cdialog_cookie_file_permissions_ok
 *
 ***************************************************************************/

static gboolean
cdialog_cookie_file_permissions_ok (const gchar *cookiefn, time_t *p_modtime)
{
	if (p_modtime)
		*p_modtime = (time_t) 0;

#ifdef G_OS_UNIX
	if (1)
	{
		struct stat st;

		if (stat (cookiefn, &st) != 0)
			return FALSE;
	
		if ((st.st_mode & 00077) != 0)
			return FALSE;

		if (p_modtime)
			*p_modtime = st.st_mtime;
	}
#endif
	
	return TRUE;
}

/***************************************************************************
 *
 *    cdialog_cookie_read
 *
 *    Reads in cookie and adds it to list, or 
 *      deletes the cookie file if it is stale
 *
 ***************************************************************************/

static void
cdialog_cookie_read (const gchar *dir, const gchar *entry, GList **p_core_list)
{
	GuiLocalCore *c;
	GError       *err = NULL;
	time_t        modtime;
	gchar        *content;
	gchar        *cookiefn, **lines;
	gsize         clen;
	pid_t         pid;
			
	cookiefn = g_build_filename (dir, entry, NULL);
			
	if (!cdialog_cookie_file_permissions_ok (cookiefn, &modtime))
	{
		g_printerr ("Core cookie '%s' has wrong permissions.\n", cookiefn);
		g_free (cookiefn);
		return;
	}
	
	if (!g_file_get_contents (cookiefn, &content, &clen, &err))
	{
		g_printerr ("Could not read core cookie '%s': %s\n", cookiefn, err->message);
		g_printerr ("   (maybe it belongs to a different user?)\n");
		g_error_free (err);
		err = NULL;
		g_free (cookiefn);
		return;
	}
	
	lines = g_strsplit (content, "\n", -1);
	g_free (content);
	content = NULL;

	/* need at least two lines: cookie and core PID */
	if (lines == NULL || lines[0] == NULL || lines[1] == NULL || strlen (lines[0]) != 64)
	{
		g_printerr ("Core cookie '%s' has wrong format.\n", cookiefn);
		g_free (cookiefn);
		g_strfreev (lines);
		return;
	}
	
#if GTK_CHECK_VERSION(2,2,0)
	pid = (pid_t) g_ascii_strtoull (lines[1], NULL, 10);
#else
	pid = (pid_t) strtoul (lines[1], NULL, 10);
#endif

#ifdef G_OS_UNIX
	if (pid > 0  &&  kill (pid, 0) == 0)
#else
	if (1)
#endif
	{
		c = g_new0 (GuiLocalCore, 1);
		
		c->cookiefn  = cookiefn;
		c->cookie    = g_strdup (lines[0]);
		c->adminport = atoi (entry + strlen (".mm-auth-cookie-"));
		c->corepid   = (guint64) pid;
		c->modtime   = modtime;

		*p_core_list = g_list_append (*p_core_list, c);
	}
	else
	{
		if (unlink (cookiefn) == 0)
		{
			g_print ("Removed stale cookie '%s' with PID %" G_GUINT64_FORMAT "\n", 
			         cookiefn, (guint64) pid);
		}
	}
	
	g_strfreev (lines);
}

/***************************************************************************
 *
 *    cdialog_scan_dir_for_cookies
 *
 ***************************************************************************/

static void
cdialog_scan_dir_for_cookies (const gchar *dir, GList **p_core_list)
{
	const gchar *entry;
	GError      *err = NULL;
	GDir        *tmpdir;

	g_return_if_fail (dir != NULL);
	
	tmpdir = g_dir_open (dir, 0, &err);
	if (err)
	{
		g_printerr ("Could not open system temp folder '%s': %s\n", dir, err->message);
		g_error_free (err);
		return;
	}
	
	while ((entry = g_dir_read_name (tmpdir)))
	{
		if (strncmp (entry, ".mm-auth-cookie-", 16) == 0)
			cdialog_cookie_read (dir, entry, p_core_list);
	}
	
	g_dir_close (tmpdir);
}


/***************************************************************************
 *
 *    cdialog_scan_dir_for_core_binaries
 *
 ***************************************************************************/

static void
cdialog_scan_dir_for_core_binaries (GuiConnectDialog *cd, const gchar *dir)
{
	const gchar *entry, *def_bin_path;
	GError      *err = NULL;
	GDir        *d;

	d = g_dir_open (dir, 0, &err);
	if (err)
	{
		/* g_print ("Error opening directory '%s' at %s: %s\n", dir, G_STRLOC, err->message); */
		g_error_free (err);
		return;
	}

	def_bin_path = opt_get_str (OPT_GUI_DEFAULT_CORE_BINARY_PATH);
	
	while ((entry = g_dir_read_name (d)))
	{
		if (g_ascii_strncasecmp (entry, "overnetclc", 10) == 0
		  || g_ascii_strncasecmp (entry, "edonkeyclc", 10) == 0
		  || g_ascii_strncasecmp (entry, "donkeyclc", 9) == 0)
		{
			gchar *fullpath = g_strconcat (dir, G_DIR_SEPARATOR_S, entry, NULL);

			if (!g_hash_table_lookup (cd->core_bin_items, fullpath)
			 && g_file_test (fullpath, G_FILE_TEST_IS_EXECUTABLE))
			{
				GtkWidget *item;

				item = gtk_menu_item_new_with_label (fullpath);

				/* string at fullpath will be valid as long as the item
				 *  exists, as it's going to be as key in the hash table */
				g_object_set_data ((GObject*) item, "core-bin-path", fullpath);

				gtk_widget_show (item);

				gtk_menu_shell_append (GTK_MENU_SHELL (cd->menu_start_core), item); 

				g_signal_connect_swapped (item, "activate",
				                          G_CALLBACK (cdialog_start_core_item_activated),
				                          cd);
				
				if (g_hash_table_size (cd->core_bin_items) == 0 
				 || (def_bin_path  &&  g_str_equal (def_bin_path, fullpath)))
				{
					gtk_option_menu_set_history ((GtkOptionMenu*) cd->optmenu_local_core_start, 0);
					gtk_menu_item_activate ((GtkMenuItem*) item);
				}

				/* hash table takes ownership of core binary path (=key) */
				g_hash_table_insert (cd->core_bin_items, fullpath, item);
				cd->core_bin_list = g_list_append (cd->core_bin_list, fullpath);
			}
			else
			{
				g_free (fullpath);
			}
		}
	}
	g_dir_close (d);
}

/***************************************************************************
 *
 *    cdialog_check_old_bins
 *
 *    Makes sure core binaries previously found still exist. If any of
 *     them disappeared, remove them from the GtkOptionMenu.
 *
 ***************************************************************************/

static void
cdialog_check_old_bins (GuiConnectDialog *cd)
{
	GList *l;

start:

	for (l = cd->core_bin_list; l; l = l->next)
	{
		const gchar *fullpath;

		fullpath = (const gchar*) l->data; 

		if (!g_file_test (fullpath, G_FILE_TEST_IS_EXECUTABLE))
		{
			GtkWidget *menuitem;
			
			cd->core_bin_list = g_list_remove (cd->core_bin_list, fullpath);
			
			menuitem = GTK_WIDGET (g_hash_table_lookup (cd->core_bin_items, fullpath));
			
			g_hash_table_remove (cd->core_bin_items, fullpath);
			
			gtk_container_remove (GTK_CONTAINER (menuitem->parent), menuitem);
			goto start;
		}
	}
}

/***************************************************************************
 *
 *    cdialog_scan_path_for_core_binaries
 *
 ***************************************************************************/

static void
cdialog_scan_path_for_core_binaries (GuiConnectDialog *cd)
{
	const gchar *path;
	gchar      **dirs, **p;
	
	path = g_getenv ("PATH");

	if (path == NULL)
#ifdef G_OS_UNIX
		path = "/usr/local/bin:/usr/bin";
#else
		path = "C:\\";
#endif

	dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, -1);
	for (p = dirs;  p && *p;  ++p)
	{
#ifdef G_OS_UNIX
		if (strncmp (*p, "~/", 2) == 0)
		{
			gchar *old = *p;
			*p = g_strdup_printf ("%s%c%s", g_getenv("HOME"), G_DIR_SEPARATOR, (*p) + 2);
			g_free (old);
		}
#endif
		
		cdialog_scan_dir_for_core_binaries (cd, *p);
	}

	g_strfreev (dirs);
	
	cdialog_check_old_bins (cd);
}


/***************************************************************************
 *
 *    connect_dialog_spawn_core
 *
 ***************************************************************************/

gboolean   
connect_dialog_spawn_core (const gchar *corepath, GError **err)
{
#ifdef G_OS_WIN32
	g_set_error (err, g_quark_from_string ("ed2k-gtk-gui-error"), 0,
	             _("Starting a core is not implemented on Windows at the moment, sorry.\n"
	               "Please start the command line client manually from a command line\n"
	               "prompt specifying the '-g' option, then set up an admin username and\n"
	               "password with 'pass bob bobpass' in the command line client, and then\n"
	               "choose 'connect to core on remote host' in the connect dialog, using\n"
		       "127.0.0.1 as the hostname where the core is running.\n"));
	return FALSE;
#else
	gchar  *cmd, *sout = NULL, *serr = NULL;
	
	g_return_val_if_fail (corepath != NULL, FALSE);

	cmd = g_strdup_printf ("%s --daemon -g -l", corepath);
	
	g_spawn_command_line_sync (cmd, &sout, &serr, NULL, err);
	
	if (sout && *sout)
		g_print ("\tcore stdout = '%s'\n", sout);
	
	if (serr && *serr)
		g_print ("\tcore stderr = '%s'\n", serr);

	
	g_free (sout);
	g_free (serr);
	
	return TRUE;
#endif
	
	g_assert_not_reached ();
}
