More gui changes

- Move progress bar into connect/start field
- Pretty up port/type boxes
- Hook better into adding clients
- Add ->quit hook for client ops
- Ensure fio_handle_clients() stays connected for GUI clients
- Stop server from repeatedly sending RUN commands

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/client.c b/client.c
index 842f225..d801236 100644
--- a/client.c
+++ b/client.c
@@ -169,6 +169,49 @@
 	}
 }
 
+struct fio_client *fio_client_add_explicit(const char *hostname, int type,
+					   int port)
+{
+	struct fio_client *client;
+
+	client = malloc(sizeof(*client));
+	memset(client, 0, sizeof(*client));
+
+	INIT_FLIST_HEAD(&client->list);
+	INIT_FLIST_HEAD(&client->hash_list);
+	INIT_FLIST_HEAD(&client->arg_list);
+	INIT_FLIST_HEAD(&client->eta_list);
+	INIT_FLIST_HEAD(&client->cmd_list);
+
+	client->hostname = strdup(hostname);
+
+	if (type == Fio_client_socket)
+		client->is_sock = 1;
+	else {
+		int ipv6;
+
+		ipv6 = type == Fio_client_ipv6;
+		if (fio_server_parse_host(hostname, &ipv6,
+						&client->addr.sin_addr,
+						&client->addr6.sin6_addr))
+			goto err;
+
+		client->port = port;
+	}
+
+	client->fd = -1;
+
+	__fio_client_add_cmd_option(client, "fio");
+
+	flist_add(&client->list, &client_list);
+	nr_clients++;
+	dprint(FD_NET, "client: added <%s>\n", client->hostname);
+	return client;
+err:
+	free(client);
+	return NULL;
+}
+
 int fio_client_add(const char *hostname, void **cookie)
 {
 	struct fio_client *existing = *cookie;
@@ -823,6 +866,8 @@
 
 	switch (cmd->opcode) {
 	case FIO_NET_CMD_QUIT:
+		if (ops->quit)
+			ops->quit(client);
 		remove_client(client);
 		free(cmd);
 		break;
@@ -981,7 +1026,7 @@
 		flist_for_each_safe(entry, tmp, &client_list) {
 			client = flist_entry(entry, struct fio_client, list);
 
-			if (!client->sent_job &&
+			if (!client->sent_job && !ops->stay_connected &&
 			    flist_empty(&client->cmd_list)) {
 				remove_client(client);
 				continue;
diff --git a/client.h b/client.h
index 4146607..0071484 100644
--- a/client.h
+++ b/client.h
@@ -42,6 +42,8 @@
 
 	uint16_t argc;
 	char **argv;
+
+	void *client_data;
 };
 
 typedef void (*client_text_op_func)(struct fio_client *client,
@@ -59,6 +61,8 @@
 
 typedef void (*client_thread_status_display_op)(char *status_message, double perc);
 
+typedef void (*client_quit_op)(struct fio_client *);
+
 struct client_ops {
 	client_text_op_func text_op;
 	client_disk_util_op_func disk_util;
@@ -66,6 +70,8 @@
 	client_group_stats_op group_stats;
 	client_eta_op eta;
 	client_probe_op probe;
+	client_quit_op quit;
+	int stay_connected;
 };
 
 extern struct client_ops fio_client_ops;
@@ -80,5 +86,18 @@
 extern void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je);
 extern void fio_client_convert_jobs_eta(struct jobs_eta *je);
 
+enum {
+	Fio_client_ipv4 = 1,
+	Fio_client_ipv6,
+	Fio_client_socket,
+};
+
+extern int fio_clients_connect(void);
+extern int fio_clients_send_ini(const char *);
+extern int fio_handle_clients(struct client_ops *ops);
+extern int fio_client_add(const char *, void **);
+extern struct fio_client *fio_client_add_explicit(const char *, int, int);
+extern void fio_client_add_cmd_option(void *, const char *);
+
 #endif
 
diff --git a/gfio.c b/gfio.c
index 34b578d..848e511 100644
--- a/gfio.c
+++ b/gfio.c
@@ -83,8 +83,8 @@
 	GtkWidget *hostname_hbox;
 	GtkWidget *hostname_label;
 	GtkWidget *hostname_entry;
+	GtkWidget *port_button;
 	GtkWidget *port_label;
-	GtkWidget *port_entry;
 	GtkWidget *hostname_combo_box; /* ipv4, ipv6 or socket */
 	GtkWidget *scrolled_window;
 	GtkWidget *textview;
@@ -93,13 +93,27 @@
 	GtkTextBuffer *text;
 	struct probe_widget probe;
 	struct eta_widget eta;
+	int connected;
 	pthread_t t;
 
-	void *cookie;
+	struct fio_client *client;
 	int nr_job_files;
 	char **job_files;
 } ui;
 
+static void gfio_set_connected(struct gui *ui, int connected)
+{
+	if (connected) {
+		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
+		ui->connected = 1;
+		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
+	} else {
+		ui->connected = 0;
+		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
+		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
+	}
+}
+
 static void gfio_text_op(struct fio_client *client,
                 FILE *f, __u16 pdu_len, const char *buf)
 {
@@ -265,6 +279,13 @@
 	gdk_threads_leave();
 }
 
+static void gfio_quit_op(struct fio_client *client)
+{
+	struct gui *ui = client->client_data;
+
+	gfio_set_connected(ui, 0);
+}
+
 struct client_ops gfio_client_ops = {
 	.text_op		= gfio_text_op,
 	.disk_util		= gfio_disk_util_op,
@@ -272,6 +293,8 @@
 	.group_stats		= gfio_group_stats_op,
 	.eta			= gfio_eta_op,
 	.probe			= gfio_probe_op,
+	.quit			= gfio_quit_op,
+	.stay_connected		= 1,
 };
 
 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
@@ -282,10 +305,9 @@
 
 static void *job_thread(void *arg)
 {
-	struct gui *ui = arg;
-
+	printf("job thread starts\n");
 	fio_handle_clients(&gfio_client_ops);
-	gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
+	printf("job thread exits\n");
 	return NULL;
 }
 
@@ -310,15 +332,13 @@
 	return ret;
 }
 
-static void start_job_thread(pthread_t *t, struct gui *ui)
+static void start_job_thread(struct gui *ui)
 {
 	if (send_job_files(ui)) {
 		printf("Yeah, I didn't really like those options too much.\n");
 		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
 		return;
 	}
-		
-	pthread_create(t, NULL, job_thread, ui);
 }
 
 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
@@ -327,14 +347,20 @@
 	struct gui *ui = data;
 
 	gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
-	start_job_thread(&ui->t, ui);
+	start_job_thread(ui);
 }
 
 static void connect_clicked(__attribute__((unused)) GtkWidget *widget,
                 gpointer data)
 {
-	fio_clients_connect();
-	gtk_widget_set_sensitive(ui.button[START_JOB_BUTTON], 1);
+	struct gui *ui = data;
+
+	if (!ui->connected) {
+		fio_clients_connect();
+		pthread_create(&ui->t, NULL, job_thread, NULL);
+		gfio_set_connected(ui, 1);
+	} else
+		gfio_set_connected(ui, 0);
 }
 
 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
@@ -342,7 +368,7 @@
 {
 	ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
 	g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
-	gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
 	gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
 	gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
 }
@@ -419,13 +445,27 @@
 	filenames = fn_glist;
 	while (filenames != NULL) {
 		const char *hostname;
+		char *typeentry;
+		gint port;
+		int type;
 
 		ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
 		ui.job_files[ui.nr_job_files] = strdup(filenames->data);
 		ui.nr_job_files++;
 
 		hostname = gtk_entry_get_text(GTK_ENTRY(ui.hostname_entry));
-		fio_client_add(hostname, &ui.cookie);
+		port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ui.port_button));
+		typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(ui.hostname_combo_box));
+		if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+			type = Fio_client_ipv4;
+		else if (!strncmp(typeentry, "IPv6", 4))
+			type = Fio_client_ipv6;
+		else
+			type = Fio_client_socket;
+		g_free(typeentry);
+
+		ui.client = fio_client_add_explicit(hostname, type, port);
+		ui.client->client_data = &ui;
 #if 0
 		if (error) {
 			report_error(error);
@@ -539,10 +579,42 @@
 	return label_widget;
 }
 
+static GtkWidget *create_text_entry(GtkWidget *hbox, GtkWidget *label, const char *defval)
+{
+	GtkWidget *text, *box;
+
+	gtk_container_add(GTK_CONTAINER(hbox), label);
+
+	box = gtk_hbox_new(FALSE, 3);
+	gtk_container_add(GTK_CONTAINER(hbox), box);
+
+	text = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(box), text, TRUE, TRUE, 0);
+	gtk_entry_set_text(GTK_ENTRY(text), "localhost");
+
+	return text;
+}
+
+static GtkWidget *create_spinbutton(GtkWidget *hbox, GtkWidget *label, double min, double max, double defval)
+{
+	GtkWidget *button, *box;
+
+	gtk_container_add(GTK_CONTAINER(hbox), label);
+
+	box = gtk_hbox_new(FALSE, 3);
+	gtk_container_add(GTK_CONTAINER(hbox), box);
+
+	button = gtk_spin_button_new_with_range(min, max, 1.0);
+	gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
+
+	gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
+	gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
+
+	return button;
+}
+
 static void init_ui(int *argc, char **argv[], struct gui *ui)
 {
-	GList *hostname_type_list = NULL;
-	char portnum[20];
 	GtkSettings *settings;
 	GtkUIManager *uimanager;
 	GtkWidget *menu, *probe, *probe_frame, *probe_box;
@@ -581,7 +653,7 @@
 	 * align top left, expand horizontally but not vertically
 	 */
 	ui->topalign = gtk_alignment_new(0, 0, 1, 0);
-	ui->topvbox = gtk_vbox_new(FALSE, 0);
+	ui->topvbox = gtk_vbox_new(FALSE, 3);
 	gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
 	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
 
@@ -589,31 +661,24 @@
 	 * Set up hostname label + entry, port label + entry,
 	 */
 	ui->hostname_hbox = gtk_hbox_new(FALSE, 0);
-	ui->hostname_label = gtk_label_new("Host:");
-	ui->hostname_entry = gtk_entry_new();
-	gtk_entry_set_text(GTK_ENTRY(ui->hostname_entry), "localhost");
+
+	ui->hostname_label = gtk_label_new("Hostname:");
+	ui->hostname_entry = create_text_entry(ui->hostname_hbox, ui->hostname_label, "localhost");
+
 	ui->port_label = gtk_label_new("Port:");
-	ui->port_entry = gtk_entry_new();
-	snprintf(portnum, sizeof(portnum) - 1, "%d", FIO_NET_PORT);
-	gtk_entry_set_text(GTK_ENTRY(ui->port_entry), (gchar *) portnum);
+	ui->port_button = create_spinbutton(ui->hostname_hbox, ui->port_label, 1, 65535, FIO_NET_PORT);
 
 	/*
 	 * Set up combo box for address type
 	 */
-	ui->hostname_combo_box = gtk_combo_new();
-	gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ui->hostname_combo_box)->entry), "IPv4");
-	hostname_type_list = g_list_append(hostname_type_list, (gpointer) "IPv4"); 
-	hostname_type_list = g_list_append(hostname_type_list, (gpointer) "local socket"); 
-	hostname_type_list = g_list_append(hostname_type_list, (gpointer) "IPv6"); 
-	gtk_combo_set_popdown_strings(GTK_COMBO(ui->hostname_combo_box), hostname_type_list);
-	g_list_free(hostname_type_list);
+	ui->hostname_combo_box = gtk_combo_box_text_new();
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ui->hostname_combo_box), "IPv4");
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ui->hostname_combo_box), "IPv6");
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ui->hostname_combo_box), "local socket");
+	gtk_combo_box_set_active(GTK_COMBO_BOX(ui->hostname_combo_box), 0);
 
-	gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_label);
-	gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_entry);
-	gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->port_label);
-	gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->port_entry);
-	gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_combo_box);
-	gtk_container_add(GTK_CONTAINER (ui->topvbox), ui->hostname_hbox);
+	gtk_container_add(GTK_CONTAINER(ui->hostname_hbox), ui->hostname_combo_box);
+	gtk_container_add(GTK_CONTAINER(ui->topvbox), ui->hostname_hbox);
 
 	probe = gtk_frame_new("Job");
 	gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
@@ -647,14 +712,6 @@
 	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
 
 	/*
-	 * Set up thread status progress bar
-	 */
-	ui->thread_status_pb = gtk_progress_bar_new();
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
-	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
-	gtk_container_add(GTK_CONTAINER(ui->topvbox), ui->thread_status_pb);
-
-	/*
 	 * Add a text box for text op messages 
 	 */
 	ui->textview = gtk_text_view_new();
@@ -680,6 +737,16 @@
 					FALSE, FALSE, 0);
 
 	add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
+
+	/*
+	 * Set up thread status progress bar
+	 */
+	ui->thread_status_pb = gtk_progress_bar_new();
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
+	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
+
+
 	gtk_widget_show_all(ui->window);
 }
 
@@ -690,6 +757,7 @@
 	if (fio_init_options())
 		return 1;
 
+	fio_debug = ~0UL;
 	init_ui(&argc, &argv, &ui);
 
 	gdk_threads_enter();
diff --git a/server.c b/server.c
index 2d9b009..a2db9dd 100644
--- a/server.c
+++ b/server.c
@@ -554,8 +554,10 @@
 
 void fio_server_idle_loop(void)
 {
-	if (!first_cmd_check)
+	if (!first_cmd_check) {
 		fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_RUN, 0, NULL);
+		first_cmd_check = 1;
+	}
 	if (server_fd != -1)
 		handle_connection(server_fd, 0);
 }
@@ -943,6 +945,46 @@
 	return sk;
 }
 
+int fio_server_parse_host(const char *host, int *ipv6, struct in_addr *inp,
+			  struct in6_addr *inp6)
+
+{
+	int ret = 0;
+
+	if (*ipv6)
+		ret = inet_pton(AF_INET6, host, inp6);
+	else
+		ret = inet_pton(AF_INET, host, inp);
+
+	if (ret != 1) {
+		struct hostent *hent;
+
+		hent = gethostbyname(host);
+		if (!hent) {
+			log_err("fio: failed to resolve <%s>\n", host);
+			return 0;
+		}
+
+		if (*ipv6) {
+			if (hent->h_addrtype != AF_INET6) {
+				log_info("fio: falling back to IPv4\n");
+				*ipv6 = 0;
+			} else
+				memcpy(inp6, hent->h_addr_list[0], 16);
+		}
+		if (!*ipv6) {
+			if (hent->h_addrtype != AF_INET) {
+				log_err("fio: lookup type mismatch\n");
+				return 0;
+			}
+			memcpy(inp, hent->h_addr_list[0], 4);
+		}
+		ret = 1;
+	}
+
+	return !(ret == 1);
+}
+
 /*
  * Parse a host/ip/port string. Reads from 'str'.
  *
@@ -961,7 +1003,7 @@
 {
 	const char *host = str;
 	char *portp;
-	int ret, lport = 0;
+	int lport = 0;
 
 	*ptr = NULL;
 	*is_sock = 0;
@@ -1022,38 +1064,10 @@
 
 	*ptr = strdup(host);
 
-	if (*ipv6)
-		ret = inet_pton(AF_INET6, host, inp6);
-	else
-		ret = inet_pton(AF_INET, host, inp);
-
-	if (ret != 1) {
-		struct hostent *hent;
-
-		hent = gethostbyname(host);
-		if (!hent) {
-			log_err("fio: failed to resolve <%s>\n", host);
-			free(*ptr);
-			*ptr = NULL;
-			return 1;
-		}
-
-		if (*ipv6) {
-			if (hent->h_addrtype != AF_INET6) {
-				log_info("fio: falling back to IPv4\n");
-				*ipv6 = 0;
-			} else
-				memcpy(inp6, hent->h_addr_list[0], 16);
-		}
-		if (!*ipv6) {
-			if (hent->h_addrtype != AF_INET) {
-				log_err("fio: lookup type mismatch\n");
-				free(*ptr);
-				*ptr = NULL;
-				return 1;
-			}
-			memcpy(inp, hent->h_addr_list[0], 4);
-		}
+	if (fio_server_parse_host(*ptr, ipv6, inp, inp6)) {
+		free(*ptr);
+		*ptr = NULL;
+		return 1;
 	}
 
 	if (*port == 0)
diff --git a/server.h b/server.h
index f2dd29f..d17c0ce 100644
--- a/server.h
+++ b/server.h
@@ -113,6 +113,7 @@
 extern int fio_net_send_simple_cmd(int, uint16_t, uint64_t, struct flist_head *);
 extern void fio_server_set_arg(const char *);
 extern int fio_server_parse_string(const char *, char **, int *, int *, struct in_addr *, struct in6_addr *, int *);
+extern int fio_server_parse_host(const char *, int *, struct in_addr *, struct in6_addr *);
 extern const char *fio_server_op(unsigned int);
 extern void fio_server_got_signal(int);
 
@@ -123,12 +124,6 @@
 extern void fio_server_send_du(void);
 extern void fio_server_idle_loop(void);
 
-extern int fio_clients_connect(void);
-extern int fio_clients_send_ini(const char *);
-extern int fio_handle_clients(struct client_ops *ops);
-extern int fio_client_add(const char *, void **);
-extern void fio_client_add_cmd_option(void *, const char *);
-
 extern int fio_recv_data(int sk, void *p, unsigned int len);
 extern int fio_send_data(int sk, const void *p, unsigned int len);
 extern void fio_net_cmd_crc(struct fio_net_cmd *);