Re: Todo list



On Sun, 21 Nov 1999, Peter Williams wrote:
>Hi everyone, sorry for being absent for a little while.
>
>The difficulty of threading is not insubstantial. However, libmutt does
>have code to do this. OTOH, we hate libmutt :-). It's something I'll look
>into after I finish the new balsa-init.c.

Debugging threaded code on top of GTK is much harder than debugging a
finite-state machine.  I have an ugly, freestanding program (GUI unfinished)
which gets mail via POP3 using gdk_input_add() and a finite-state machine.  It
is extensible to also support direct SMTP in the same process, and with the
right GUI it can even handle multiple simultaneous downloads (like web browsers
do).

My code would fit into Balsa pretty well if I can puzzle out libmutt and the
Balsa main GUI well enough... the main problem I see is that I must know how to
add a message to a folder while the user is viewing that folder.  My code
would not block the main GUI; it should in fact be about as responsive during a
download of multiple POP3 streams as when no downloads are in progress.  (Take
THAT, Outlook Express! :-)

The main advantage of doing it this way is that it makes porting Balsa to older
Linux versions (libc5) as well as other non-threadsafe environments easier. 
Since gdk has to run a select()-loop anyway, adding on to it using
gdk_input_add() is not adding much overhead.  Also, you know that no other
execution threads are running during your "time-slices" in the dispatching
function; this IMHO is a major reduction in Tylenol-usage.

Anyway, below is my code.  See what you all think...

/* Balsa E-Mail Client
 * 
 * mailxfer.c -- get mail via pop3 (and send via smtp, someday...)
 * 
 * Copyright 1999, Chris Gonnerman
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

/*
	TODO:
	
	* Finish build_gui and add progress widgets to XferState.
	* Dialogs for all messages.
*/

#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>

#define MAXNAMELEN	256
#define MAXIOBUF	2048
#define STRBUFLEN	256
#define MAXJOBS		1		/* current gui can't do more than one... yet. */

#define MAILXFER_C

static gint launch_next_job(void);

typedef struct xfer_state {

	gint state;
	gint fd;
	gint tag;

	gchar host[MAXNAMELEN+1];
	gint port;

	gchar user[MAXNAMELEN+1];
	gchar pass[MAXNAMELEN+1];

	gint msg_cnt, current_msg;
	glong byte_cnt;

	gchar linebuf[MAXIOBUF+1];
	gint linepos;

	gint (*dispatch)(struct xfer_state *st);

} XferState;

static GSList *jobqueue = NULL;
static gint jobs_running = 0;

static gint gui_built = 0;

static GtkWidget *xferwin = NULL;

enum pop_states {
	PS_HOLD = 0,
	PS_USER,
	PS_PASS,
	PS_STAT,
	PS_STAT_REPLY,
	PS_RETR,
	PS_GET_MSG,
	PS_ERROR,
	PS_STATE_COUNT
};


static gint hide_xfer_gui(GtkWidget *w, gpointer gdata)
{
	gtk_widget_hide(w);

	return(TRUE);
}


static void build_gui()
{
	xferwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);

	gtk_window_set_wmclass(GTK_WINDOW(xferwin), "MailXFer", "mailxfer");
	gtk_window_set_title(GTK_WINDOW(xferwin), "Transferring Mail...");

	gtk_signal_connect(GTK_OBJECT(xferwin), "delete_event", 
		GTK_SIGNAL_FUNC(hide_xfer_gui), NULL);

	gtk_widget_show(xferwin);

	gui_built = 1;
}


static gint pop3error(XferState *st)
{
	gchar buf[STRBUFLEN+1];

	g_warning("POP3 Error -- The Server said:\n");

	snprintf(buf, sizeof(buf), "\t%s\n", st->linebuf);

	g_warning(buf);

	st->state = PS_ERROR;

	return 0;
}


static gint pop_dispatch(XferState *st)
{
	gchar buf[STRBUFLEN+1];

	fputs("pop3 dispatch:  ", stdout);
	fputs(st->linebuf, stdout);
	fputs("\n", stdout);

	switch(st->state) {

	case PS_ERROR :
		g_warning("should not have called pop_dispatch() again!\n");
		return 0;
		break;

	case PS_USER :
		if(strncmp(st->linebuf, "+OK", 3) == 0) {
			snprintf(buf, sizeof(buf), "user %s\r\n", st->user);
			write(st->fd, buf, strlen(buf));
			st->state = PS_PASS;
		} else 
			return pop3error(st);
		break;

	case PS_PASS :
		if(strncmp(st->linebuf, "+OK", 3) == 0) {
			snprintf(buf, sizeof(buf), "pass %s\r\n", st->pass);
			write(st->fd, buf, strlen(buf));
			st->state = PS_STAT;
		} else 
			return pop3error(st);
		break;

	case PS_STAT :
		if(strncmp(st->linebuf, "+OK", 3) == 0) {
			write(st->fd, "stat\r\n", 6);
			st->state = PS_STAT_REPLY;
		} else 
			return pop3error(st);
		break;

	case PS_STAT_REPLY :
		if(strncmp(st->linebuf, "+OK", 3) == 0) {
			sscanf(st->linebuf, "+OK %d %ld", 
				&(st->msg_cnt), &(st->byte_cnt));
			printf("msg_cnt = %d, byte_cnt = %ld\n", 
				st->msg_cnt, st->byte_cnt);
			if(st->msg_cnt > 0) {
				st->state = PS_RETR;
				st->current_msg = 1;
				write(st->fd, "retr 1\r\n", 8);
			} else {
				g_warning("no mail.\n");
				st->state = PS_ERROR;
					/* not really, just want the warning */
				return 0;
			}
		} else 
			return pop3error(st);
		break;

	case PS_RETR :
		if(strncmp(st->linebuf, "+OK", 3) == 0) {
			st->state = PS_GET_MSG;
		} else {
			st->state = PS_ERROR; /* TODO: need error message here */
			return 0;
		}
		break;

	case PS_GET_MSG :
		if(st->linebuf[0] == '.' && st->linebuf[1] == '\r') {
			if(st->current_msg >= st->msg_cnt) {
				st->state = PS_ERROR;
				write(st->fd, "quit\r\n", 6);
				return 0;
			} else {
				st->current_msg++;
				sprintf(buf, "retr %d\r\n", st->current_msg);
				write(st->fd, buf, strlen(buf));
				st->state = PS_RETR;
			}
		} else {
			/* TODO: save line here */
		}
		break;
	}

	return 1;
}


static void get_data(gpointer gdata, gint fd, GdkInputCondition condition)
{
	XferState *st = (XferState *)gdata;
	gint l, i, rc;
	gint dispcount = 0;
	char inbuf[MAXIOBUF];

	if(condition & GDK_INPUT_EXCEPTION) {
		printf("exception on fd = %d, closing...\n", fd);
		gdk_input_remove(st->tag);
		close(fd);
		g_free((void *)st);
		jobs_running--;
		return;
	}

	if(condition & GDK_INPUT_READ) {
		l = read(fd, inbuf, MAXIOBUF);
		printf("read %d characters from fd = %d\n", l, fd);
		if(l == 0) {
			printf("EOF, closing fd = %d\n", fd);
			gdk_input_remove(st->tag);
			close(fd);
			g_free((void *)st);
			jobs_running--;
		}

		for(i = 0; i < l; i++) {
			if(inbuf[i] == '\n') {
				rc = st->dispatch(st);
				if(!rc) {
					printf("Dispatcher says to quit.\n");
					g_free((void *)st);
					jobs_running--;
					close(fd);
					l = -1;
					launch_next_job();
				} else {
					st->linepos = 0;
					st->linebuf[0] = '\0';
					dispcount++;
				}
			} else {
				if(st->linepos == MAXIOBUF) {
					fputs("dispatch error... line length exceeded.\n", stdout);
					fputs("... buffer cleared ...\n", stdout);
					st->linepos = 0;
					st->linebuf[0] = '\0';
				}
				st->linebuf[st->linepos++] = inbuf[i];
				st->linebuf[st->linepos] = '\0';
			}
		}

		printf("*** dispatched %d lines total\n", dispcount);
		printf("*** %d characters remain\n", st->linepos);

		return;
	}
}


static XferState *new_xfer_state()
{
	XferState *st;

	if((st = (XferState *)g_malloc(sizeof(struct xfer_state))) == NULL) {
		g_warning("g_malloc failed in new_xfer_state()\n");
		return NULL;
	}

	memset((void *)st, 0, sizeof(struct xfer_state));

	return st;
}


static gint launch_next_job()
{
	struct sockaddr_in addr;
	struct hostent *host_st;
	XferState *st;
	unsigned long int a;

	/* dequeue job */

	if(jobs_running >= MAXJOBS)
		return 0;

	if(jobqueue == NULL)
		return 0;

	st = (XferState *)(jobqueue->data);

	jobqueue = g_slist_remove(jobqueue, (gpointer)st);
	jobs_running++;

	/* parse hostname/address */

	memset((char *)&addr, 0, sizeof(struct sockaddr_in));

	if((a = inet_addr(st->host)) == -1) {
		if((host_st = gethostbyname(st->host)) == NULL) {
			g_warning("Can't get hostname; exiting...\n");
			g_free(st);
			jobs_running--;
			return launch_next_job();
		}
		memcpy((void *)&addr.sin_addr, host_st->h_addr, host_st->h_length);
		addr.sin_family = host_st->h_addrtype;
	} else {
		memcpy((void *)&addr.sin_addr, (void *)&a, sizeof(a));
		addr.sin_family = AF_INET;
	}

	addr.sin_port = st->port;

	if((st->fd = socket(host_st->h_addrtype, SOCK_STREAM, 0)) < 0) {
		g_warning("Can't create socket; exiting...\n");
		g_free(st);
		jobs_running--;
		return -1;
	}

	if(connect(st->fd, (struct sockaddr *)&addr, 
		sizeof(struct sockaddr_in)) < 0) 
	{
		g_warning("Connect failed; exiting...\n");
		g_free(st);
		jobs_running--;
		close(st->fd);
		return -1;
	}

	st->state = PS_USER;

	st->tag = gdk_input_add(st->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, 
		get_data, (gpointer)st);

	return 0;
}


gint queue_pop_download(gchar *host, gchar *port, gchar *user, gchar *pass)
{
	gint s;
	XferState *st;
	struct servent *port_st;
	gint nport;
	gint startup = 0;

	if(!gui_built)
		build_gui();

	if(xferwin == NULL)	/* TODO: set errno here? */
		return -1;

	gtk_widget_show(xferwin);

	if((st = new_xfer_state()) == NULL) {
		g_warning("error allocating memory; exiting...\n");
		return -1;
	}

	if((nport = atoi(port)) < 1) {
		if((port_st = getservbyname(port, "tcp")) == NULL) {
			g_warning("Can't get port number; exiting...\n");
			g_free(st);
			return -1;
		}
		st->port = port_st->s_port;
	} else
		st->port = htons(nport);
	
	strncpy(st->host, host, MAXNAMELEN);
	strncpy(st->user, user, MAXNAMELEN);
	strncpy(st->pass, pass, MAXNAMELEN);

	st->dispatch = pop_dispatch;

	jobqueue = g_slist_append(jobqueue, (gpointer)st);

	launch_next_job();

	return 0;
}


/*
	this is a test-rig, which should be deleted before final implementation.
*/
int main(int argc, char **argv)
{
	gtk_init(&argc, &argv);

	queue_pop_download("localhost", "pop3", "username", "password");

	gtk_main();

	exit(0);
}


/*
# Makefile

pop3test: mailxfer.o
	gcc -o pop3test mailxfer.o `gtk-config --libs`

mailxfer.o: mailxfer.c
	gcc -c mailxfer.c `gtk-config --cflags`
*/

/* end of file. */

pop3test: mailxfer.o
	gcc -o pop3test mailxfer.o `gtk-config --libs`

mailxfer.o: mailxfer.c
	gcc -c mailxfer.c `gtk-config --cflags`



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]