changeset 4514:7521e29658bc

[gaim-migrate @ 4792] Of course, file transfer wasn't really gone.. I'm trying my hardest to bring on the end of the world (see the roadmap at http://gaim.sf.net/roadmap.png). File transfer is being rewritten. This isn't the finished implementation, but it's enough to let us get the prpls working. There is now a file transfer dialog, which will appear when you get a new transfer request or when you go to Tools -> File Transfers. This is of course core/UI split. I'll also be working on documentation on how to write FT support in a prpl. Oh, and I'll get resumes and transfer batches done when school isn't breathing down my back. Only DCC receive in IRC currently works. Sorry. We'll get the other prpls working soon, as well as send. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Tue, 04 Feb 2003 06:57:35 +0000
parents adb0245e1dfc
children 9b9737a00a96
files src/Makefile.am src/buddy.c src/core.h src/ft.c src/ft.h src/gtkcellrendererprogress.c src/gtkft.c src/gtkft.h src/main.c src/protocols/irc/irc.c src/prpl.h src/stock.c src/stock.h src/ui.h
diffstat 14 files changed, 1832 insertions(+), 686 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile.am	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/Makefile.am	Tue Feb 04 06:57:35 2003 +0000
@@ -19,11 +19,16 @@
 		dnd-hints.c \
 		dnd-hints.h \
 		ft.c \
+		ft.h \
 		gaim.h \
 		gaim-socket.h \
 		gaimrc.c \
+		gtkcellrendererprogress.c \
+		gtkcellrendererprogress.h \
 		gtkconv.c \
 		gtkconv.h \
+		gtkft.c \
+		gtkft.h \
 		gtkimhtml.c \
 		gtkimhtml.h \
 		gtkutils.c \
--- a/src/buddy.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/buddy.c	Tue Feb 04 06:57:35 2003 +0000
@@ -2524,6 +2524,9 @@
 	gaim_new_item_from_stock(menu, _("_Preferences..."), GTK_STOCK_PREFERENCES,
 				  G_CALLBACK(show_prefs), NULL, 'p', GDK_CONTROL_MASK, "Ctl+P");
 
+	gaim_new_item_from_stock(menu, _("_File Transfers..."), GTK_STOCK_REVERT_TO_SAVED,
+				G_CALLBACK(gaim_gtkxfer_dialog_show), NULL, 0, 0, NULL);
+
 	gaim_separator(menu);
 
 	protomenu = gtk_menu_new();
--- a/src/core.h	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/core.h	Tue Feb 04 06:57:35 2003 +0000
@@ -49,6 +49,7 @@
 
 #include "multi.h"
 #include "conversation.h"
+#include "ft.h"
 
 /* Really user states are controlled by the PRPLs now. We just use this for event_away */
 #define UC_UNAVAILABLE  1
--- a/src/ft.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/ft.c	Tue Feb 04 06:57:35 2003 +0000
@@ -1,7 +1,9 @@
-/*
- * gaim - file transfer functions
+/**
+ * @file ft.c The file transfer interface.
  *
- * Copyright (C) 2002, Wil Mahan <wtm2@duke.edu>
+ * gaim
+ *
+ * Copyright (C) 2002-2003, Christian Hammond <chipx86@gnupdate.org>
  * 
  * 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
@@ -28,666 +30,627 @@
 #include <errno.h>
 #include <string.h>
 
-#define FT_BUFFER_SIZE (4096)
-
 #include <gtk/gtk.h>
 #include "gaim.h"
 #include "proxy.h"
-#include "prpl.h"
 
-#ifdef _WIN32
-#include "win32dep.h"
-#endif
-
-/* Completely describes a file transfer.  Opaque to callers. */
-struct file_transfer {
-	enum { FILE_TRANSFER_TYPE_SEND, FILE_TRANSFER_TYPE_RECEIVE } type;
-
-	enum {
-		FILE_TRANSFER_STATE_ASK, /* waiting for confirmation */
-		FILE_TRANSFER_STATE_CHOOSEFILE, /* waiting for file dialog */
-		FILE_TRANSFER_STATE_TRANSFERRING, /* in actual transfer */
-		FILE_TRANSFER_STATE_INTERRUPTED, /* other user canceled */
-		FILE_TRANSFER_STATE_CANCELED, /* we canceled */
-		FILE_TRANSFER_STATE_DONE, /* transfer complete */
-		FILE_TRANSFER_STATE_CLEANUP /* freeing memory */
-	} state;
-
-	char *who;
+static struct gaim_xfer_ui_ops *xfer_ui_ops = NULL;
 
-	/* file selection dialog */
-	GtkWidget *w;
-	char **names;
-	int *sizes;
-	char *dir;
-	char *initname;
-	FILE *file;
-
-	/* connection info */
-	struct gaim_connection *gc;
-	int fd;
-	int watcher;
-
-	/* state */
-	int totfiles;
-	int filesdone;
-	int totsize;
-	int bytessent;
-	int bytesleft;
-};
-
-
-
-static int ft_choose_file(struct file_transfer *xfer);
-static void ft_cancel(struct file_transfer *xfer);
-static void ft_delete(struct file_transfer *xfer);
-static void ft_callback(gpointer data, gint source, GaimInputCondition condition);
-static void ft_nextfile(struct file_transfer *xfer);
-static int ft_mkdir(const char *name);
-static int ft_mkdir_help(char *dir);
-static gboolean ft_choose_file_close(GtkWidget *widget, GdkEvent *event,
-									 gpointer user_data);
-
-static struct file_transfer *ft_new(int type, struct gaim_connection *gc,
-		const char *who)
+struct gaim_xfer *
+gaim_xfer_new(struct gaim_account *account, GaimXferType type,
+			  const char *who)
 {
-	struct file_transfer *xfer = g_new0(struct file_transfer, 1);
-	xfer->type = type;
-	xfer->state = FILE_TRANSFER_STATE_ASK;
-	xfer->gc = gc;
-	xfer->who = g_strdup(who);
-	xfer->filesdone = 0;
-	xfer->w = NULL;
-
-	return xfer;
-}
+	struct gaim_xfer *xfer;
 
-struct file_transfer *transfer_in_add(struct gaim_connection *gc,
-		const char *who, const char *initname, int totsize,
-		int totfiles, const char *msg)
-{
-	struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_RECEIVE, gc,
-			who);
-	char *buf;
-	char *sizebuf;
-	static const char *sizestr[4] = { "bytes", "KB", "MB", "GB" };
-	float sizemag = (float)totsize;
-	int szindex = 0;
-
-	xfer->initname = g_strdup(initname);
-	xfer->totsize = totsize;
-	xfer->totfiles = totfiles;
-	xfer->filesdone = 0;
-
-	/* Ask the user whether he or she accepts the transfer. */
-	while ((szindex < 4) && (sizemag > 1024)) {
-		sizemag /= 1024;
-		szindex++;
-	}
+	if (account == NULL || type == GAIM_XFER_UNKNOWN || who == NULL)
+		return NULL;
 
-	if (totsize == -1)
-		sizebuf = g_strdup_printf(_("Unkown"));
-	else
-		sizebuf = g_strdup_printf("%.3g %s", sizemag, sizestr[szindex]);
-
-	if (xfer->totfiles == 1) {
-		buf = g_strdup_printf(_("%s requests that %s accept a file: %s (%s)"),
-		who, xfer->gc->username, initname, sizebuf);
-	} else {
-		buf = g_strdup_printf(_("%s requests that %s accept %d files: %s (%s)"),
-		who, xfer->gc->username, xfer->totfiles,
-		initname, sizebuf);
-	}
-
-	g_free(sizebuf);
+	xfer = g_malloc0(sizeof(struct gaim_xfer));
 
-	if (msg) {
-		char *newmsg = g_strconcat(buf, ": ", msg, NULL);
-		g_free(buf);
-		buf = newmsg;
-	}
-
-	do_ask_dialog(buf, NULL, xfer, _("Accept"), ft_choose_file, _("Cancel"), ft_cancel, NULL, FALSE);
-	g_free(buf);
-
-	return xfer;
-}
-
-struct file_transfer *transfer_out_add(struct gaim_connection *gc,
-		const char *who)
-{
-	struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_SEND, gc,
-			who);
-
-	ft_choose_file(xfer);
+	xfer->type    = type;
+	xfer->account = account;
+	xfer->who     = g_strdup(who);
+	xfer->ui_ops  = gaim_get_xfer_ui_ops();
 
 	return xfer;
 }
 
-/* We canceled the transfer, either by declining the initial
- * confirmation dialog or canceling the file dialog.
- */
-static void ft_cancel(struct file_transfer *xfer)
+void
+gaim_xfer_destroy(struct gaim_xfer *xfer)
 {
-	debug_printf("** ft_cancel\n");
+	struct gaim_xfer_ui_ops *ui_ops;
 
-	/* Make sure we weren't aborted while waiting for
-	 * confirmation from the user.
-	 */
-	if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) {
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, NULL);
+	if (xfer == NULL)
+		return;
+
+	if (xfer->bytes_remaining > 0) {
+		gaim_xfer_cancel(xfer);
 		return;
 	}
 
-	xfer->state = FILE_TRANSFER_STATE_CANCELED;
-	if (xfer->w) {
-		gtk_widget_destroy(xfer->w);
-		xfer->w = NULL;
-	}
+	ui_ops = gaim_xfer_get_ui_ops(xfer);
 
-	if (xfer->gc->prpl->file_transfer_cancel)
-		xfer->gc->prpl->file_transfer_cancel(xfer->gc, xfer);
-
-	ft_delete(xfer);
-}
+	if (ui_ops != NULL && ui_ops->destroy != NULL)
+		ui_ops->destroy(xfer);
 
-/* This is called when the other user aborts the transfer,
- * possibly in the middle of a transfer.
- */
-int transfer_abort(struct file_transfer *xfer, const char *why)
-{
-	if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) {
-		/* If for some reason we have already been
-		 * here and are waiting on some event before
-		 * cleaning up, but we get another abort request,
-		 * we don't need to do anything else.
-		 */
-		return 1;
-	}
-	else if (xfer->state == FILE_TRANSFER_STATE_ASK) {
-		/* Kludge:  since there is no way to kill a
-		 * do_ask_dialog() window, we just note the
-		 * status here and clean up after the user
-		 * makes a selection.
-		 */
-		xfer->state = FILE_TRANSFER_STATE_INTERRUPTED;
-		return 1;
-	}
-	else if (xfer->state == FILE_TRANSFER_STATE_TRANSFERRING) {
-		if (xfer->watcher) {
-			gaim_input_remove(xfer->watcher);
-			xfer->watcher = 0;
-		}
-		if (xfer->file) {
-			fclose(xfer->file);
-			xfer->file = NULL;
-		}
-		/* XXX theoretically, there is a race condition here,
-		 * because we could be inside ft_callback() when we
-		 * free xfer below, with undefined results.  Since
-		 * we use non-blocking IO, this doesn't seem to be
-		 * a problem, but it still makes me nervous--I don't
-		 * know how to fix it other than using locks, though.
-		 * -- wtm
-		 */
-	}
-	else if (xfer->state == FILE_TRANSFER_STATE_CHOOSEFILE) {
-		/* It's safe to clean up now.  Just make sure we
-		 * destroy the dialog window first.
-		 */
-		if (xfer->w) {
-			g_signal_handlers_disconnect_by_func(G_OBJECT(xfer->w),
-					G_CALLBACK(ft_choose_file_close), xfer);
-			gtk_widget_destroy(xfer->w);
-			xfer->w = NULL;
-		}
-	}
+	g_free(xfer->who);
+	g_free(xfer->filename);
 
-	/* Let the user know that we were aborted, unless we already
-	 * finished or the user aborted first.
-	 */
-	/* if ((xfer->state != FILE_TRANSFER_STATE_DONE) &&
-			(xfer->state != FILE_TRANSFER_STATE_CANCELED)) { */
-	if (why) {
-		char *msg;
-
-		if (xfer->type == FILE_TRANSFER_TYPE_SEND)
-			msg = g_strdup_printf(_("File transfer to %s aborted."), xfer->who);
-		else
-			msg = g_strdup_printf(_("File transfer from %s aborted."), xfer->who);
-		do_error_dialog(msg, why, GAIM_ERROR);
-		g_free(msg);
-	}
+	if (xfer->remote_ip != NULL) g_free(xfer->remote_ip);
+	if (xfer->local_ip  != NULL) g_free(xfer->local_ip);
 
-	ft_delete(xfer);
-
-	return 0;
-}
-
+	if (xfer->dest_filename != NULL)
+		g_free(xfer->dest_filename);
 
-static void ft_delete(struct file_transfer *xfer)
-{
-	if (xfer->names)
-		g_strfreev(xfer->names);
-	if (xfer->initname)
-		g_free(xfer->initname);
-	if (xfer->who)
-		g_free(xfer->who);
-	if (xfer->sizes)
-		g_free(xfer->sizes);
 	g_free(xfer);
 }
 
-static void ft_choose_ok(gpointer a, struct file_transfer *xfer) {
-	gboolean exists, is_dir;
-	struct stat st;
-	const char *err = NULL;
-	
-	xfer->names = gtk_file_selection_get_selections(GTK_FILE_SELECTION(xfer->w));
-	exists = !stat(*xfer->names, &st);
-	is_dir = (exists) ? S_ISDIR(st.st_mode) : 0;
-
-	if (exists) {
-		if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE)
-			/* XXX overwrite/append/cancel prompt */
-			err = _("That file already exists; please choose another name.");
-		else { /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */
-			char **cur;
-			/* First find the total number of files,
-			 * so we know how much space to allocate.
-			 */
-			xfer->totfiles = 0;
-			for (cur = xfer->names; *cur; cur++) {
-				xfer->totfiles++;
-			}
-
-			/* Now get sizes for each file. */
-			xfer->totsize = st.st_size;
-			xfer->sizes = g_malloc(xfer->totfiles
-					* sizeof(*xfer->sizes));
-			xfer->sizes[0] = st.st_size;
-			for (cur = xfer->names + 1; *cur; cur++) {
-				exists = !stat(*cur, &st);
-				if (!exists) {
-					err = _("File not found.");
-					break;
-				}
-				xfer->sizes[cur - xfer->names] =
-					st.st_size;
-				xfer->totsize += st.st_size;
-			}
-		}
-	}
-	else { /* doesn't exist */
-		if (xfer->type == FILE_TRANSFER_TYPE_SEND)
-			err = _("File not found.");
-		else if (xfer->totfiles > 1) {
-			if (!xfer->names[0] || xfer->names[1]) {
-				err = _("You may only choose one new directory.");
-			}
-			else {
-				if (ft_mkdir(*xfer->names))
-					err = _("Unable to create directory.");
-				else
-					xfer->dir = g_strconcat(xfer->names[0],
-						"/", NULL);
-			}
-		}
-	}
-
-	if (err)
-		do_error_dialog(err, NULL, GAIM_ERROR);
-	else {
-		/* File name looks valid */
-		gtk_widget_destroy(xfer->w);
-		xfer->w = NULL;
+void
+gaim_xfer_request(struct gaim_xfer *xfer)
+{
+	struct gaim_xfer_ui_ops *ui_ops;
 
-		if (xfer->type == FILE_TRANSFER_TYPE_SEND) {
-			char *desc;
-			if (xfer->totfiles == 1)
-				desc = *xfer->names;
-			else
-				/* XXX what else? */
-				desc = "*";
-				/* desc = g_path_get_basename(g_path_get_dirname(*xfer->names)); */
-			xfer->gc->prpl->file_transfer_out(xfer->gc, xfer,
-					desc, xfer->totfiles,
-					xfer->totsize);
-		}
-		else
-			xfer->gc->prpl->file_transfer_in(xfer->gc, xfer,
-					0); /* XXX */
-	}
-}
+	if (xfer == NULL || xfer->ops.init == NULL)
+		return;
 
-/* Called on outgoing transfers to get information about the
- * current file.
- */
-int transfer_get_file_info(struct file_transfer *xfer, int *size,
-		char **name)
-{
-	*size = xfer->sizes[xfer->filesdone];
-	*name = xfer->names[xfer->filesdone];
-	return 0;
-}
-
-static gboolean
-ft_choose_file_close(GtkWidget *widget, GdkEvent *event, gpointer user_data)
-{
-	ft_cancel((struct file_transfer *)user_data);
-
-	return FALSE;
-}
-
-static void
-ft_cancel_button_cb(GtkButton *button, gpointer user_data)
-{
-	ft_cancel((struct file_transfer *)user_data);
-}
+	ui_ops = gaim_get_xfer_ui_ops();
 
-static int ft_choose_file(struct file_transfer *xfer)
-{
-	char *curdir = g_get_current_dir(); /* should be freed */
-	char *initstr;
-
-	/* If the connection is interrupted while we are waiting
-	 * for do_ask_dialog(), then we can't clean up until we
-	 * get here, after the user makes a selection.
-	 */
-	if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) {
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, NULL);
-		return 1;
-	}
+	if (ui_ops == NULL || ui_ops->request_file == NULL)
+		return;
 
-	xfer->state = FILE_TRANSFER_STATE_CHOOSEFILE;
-	if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE)
-		xfer->w = gtk_file_selection_new(_("Gaim - Save As..."));
-	else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ {
-		xfer->w = gtk_file_selection_new(_("Gaim - Open..."));
-		gtk_file_selection_set_select_multiple(GTK_FILE_SELECTION(xfer->w),
-				1);
-	}
-
-	if (xfer->initname) {
-		initstr = g_strdup_printf("%s/%s", curdir, xfer->initname);
-	} else 
-		initstr = g_strconcat(curdir, "/", NULL);
-	g_free(curdir);
-
-	gtk_file_selection_set_filename(GTK_FILE_SELECTION(xfer->w),
-			initstr);
-	g_free(initstr);
-
-	g_signal_connect(G_OBJECT(xfer->w), "delete_event",
-					 G_CALLBACK(ft_choose_file_close), xfer);
-	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->cancel_button), "clicked",
-					 G_CALLBACK(ft_cancel_button_cb), xfer);
-	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->ok_button), "clicked",
-					 G_CALLBACK(ft_choose_ok), xfer);
-
-	gtk_widget_show(xfer->w);
-
-	return 0;
+	ui_ops->request_file(xfer);
 }
 
-static int ft_open_file(struct file_transfer *xfer, const char *filename,
-		int offset)
+void
+gaim_xfer_request_accepted(struct gaim_xfer *xfer, char *filename)
 {
-	char *err = NULL;
-
-	if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) {
-		xfer->file = fopen(filename,
-				(offset > 0) ? "a" : "w");
-
-		if (!xfer->file)
-			err = g_strdup_printf(_("Could not open %s for writing: %s"),
-					filename, g_strerror(errno));
-
-	}
-	else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ {
-		xfer->file = fopen(filename, "r");
-		if (!xfer->file)
-			err = g_strdup_printf(_("Could not open %s for reading: %s"),
-					filename, g_strerror(errno));
-	}
-
-	if (err) {
-		do_error_dialog(err, NULL, GAIM_ERROR);
-		g_free(err);
-		return -1;
-	}
-
-	fseek(xfer->file, offset, SEEK_SET);
-
-	return 0;
-}
-
-/* Takes a full file name, and creates any directories above it
- * that don't exist already.
- */
-static int ft_mkdir(const char *name) {
-	int ret = 0;
-	struct stat st;
-	mode_t m = umask(0077);
-	char *dir;
-
-	dir = g_path_get_dirname(name);
-	if (stat(dir, &st))
-		ret = ft_mkdir_help(dir);
-
-	g_free(dir);
-	umask(m);
-	return ret;
-}
-
-/* Two functions, one recursive, just to make a directory.  Yuck. */
-static int ft_mkdir_help(char *dir) {
-	int ret;
-
-	ret = mkdir(dir, 0775);
-
-	if (ret) {
-		char *index = strrchr(dir, G_DIR_SEPARATOR);
-		if (!index)
-			return -1;
-		*index = '\0';
-		ret = ft_mkdir_help(dir);
-		*index = G_DIR_SEPARATOR;
-		if (!ret)
-			ret = mkdir(dir, 0775);
-	}
-
-	return ret;
-}
- 
-int transfer_in_do(struct file_transfer *xfer, int fd,
-		const char *filename, int size)
-{
-	char *fullname;
-
-	xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
-	xfer->fd = fd;
-	xfer->bytesleft = size;
-
-	/* XXX implement resuming incoming transfers */
-#if 0
-	if (xfer->sizes)
-		xfer->bytesleft -= xfer->sizes[0];
-#endif
-
-	/* Security check */
-	if (g_strrstr(filename, "..")) {
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, _("Invalid incoming filename component"));
-		return -1;
-	}
-
-	if (xfer->totfiles > 1)
-		fullname = g_strconcat(xfer->dir, filename, NULL);
-	else
-		/* Careful:  filename is the name on the *other*
-		 * end; don't use it here. */
-		fullname = g_strdup(xfer->names[xfer->filesdone]);
+	GaimXferType type;
 
-	
-	if (ft_mkdir(fullname)) {
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, _("Invalid incoming filename"));
-		return -1;
-	}
-
-	if (!ft_open_file(xfer, fullname, 0)) {
-		/* Special case:  if we are receiving an empty file,
-		 * we would never enter the callback.  Just avoid the
-		 * callback altogether.
-		 */
-		if (xfer->bytesleft == 0)
-			ft_nextfile(xfer);
-		else
-			xfer->watcher = gaim_input_add(fd,
-					GAIM_INPUT_READ,
-					ft_callback, xfer);
-	} else {
-		/* Error opening file */
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, NULL);
-		g_free(fullname);
-		return -1;
-	}
-
-	g_free(fullname);
-	return 0;
-}
-
-int transfer_out_do(struct file_transfer *xfer, int fd, int offset) {
-	xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
-	xfer->fd = fd;
-	xfer->bytesleft = xfer->sizes[xfer->filesdone] - offset;
+	if (xfer == NULL || filename == NULL) {
 
-	if (!ft_open_file(xfer, xfer->names[xfer->filesdone], offset)) {
-		/* Special case:  see transfer_in_do().
-		 */
-		if (xfer->bytesleft == 0)
-			ft_nextfile(xfer);
-		else
-			xfer->watcher = gaim_input_add(fd,
-					GAIM_INPUT_WRITE, ft_callback,
-					xfer);
-	}
-	else {
-		/* Error opening file */
-		xfer->state = FILE_TRANSFER_STATE_CLEANUP;
-		transfer_abort(xfer, NULL);
-		return -1;
-	}
-
-	return 0;
-}
-
-static void ft_callback(gpointer data, gint source,
-		GaimInputCondition condition)
-{
-	struct file_transfer *xfer = (struct file_transfer *)data;
-	int rt;
-	char *buf = NULL;
-
-	if (condition & GAIM_INPUT_READ) {
-		if (xfer->gc->prpl->file_transfer_read)
-			rt = xfer->gc->prpl->file_transfer_read(xfer->gc, xfer,
-													xfer->fd, &buf);
-		else {
-			buf = g_new0(char, MIN(xfer->bytesleft, FT_BUFFER_SIZE));
-			rt = read(xfer->fd, buf, MIN(xfer->bytesleft, FT_BUFFER_SIZE));
-		}
-
-		/* XXX What if the transfer is interrupted while we
-		 * are inside read()?  How can this be handled safely?
-		 * -- wtm
-		 */
-		if (rt > 0) {
-			xfer->bytesleft -= rt;
-			fwrite(buf, 1, rt, xfer->file);
-		}
-
-	}
-	else /* (condition & GAIM_INPUT_WRITE) */ {
-		int remain = MIN(xfer->bytesleft, FT_BUFFER_SIZE);
-
-		buf = g_new0(char, remain);
-
-		fread(buf, 1, remain, xfer->file);
-
-		if (xfer->gc->prpl->file_transfer_write)
-			rt = xfer->gc->prpl->file_transfer_write(xfer->gc, xfer, xfer->fd,
-													 buf, remain);
-		else
-			rt = write(xfer->fd, buf, remain);
-
-		if (rt > 0)
-			xfer->bytesleft -= rt;
-	}
-
-	if (rt < 0) {
-		if (buf != NULL)
-			g_free(buf);
+		if (filename != NULL)
+			g_free(filename);
 
 		return;
 	}
 
-	xfer->bytessent += rt;
+	type = gaim_xfer_get_type(xfer);
+
+	if (type == GAIM_XFER_SEND) {
+		struct stat sb;
+
+		/* Check the filename. */
+		if (g_strrstr(filename, "..")) {
+			char *msg;
 
-	if (xfer->gc->prpl->file_transfer_data_chunk)
-		xfer->gc->prpl->file_transfer_data_chunk(xfer->gc, xfer, buf, rt);
+			msg = g_strdup_printf(_("%s is not a valid filename.\n"),
+								  filename);
+
+			gaim_xfer_error(type, xfer->who, msg);
+
+			g_free(msg);
+			g_free(filename);
+
+			return;
+		}
 
-	if (rt > 0 && xfer->bytesleft == 0) {
-		/* We are done with this file! */
-		gaim_input_remove(xfer->watcher);
-		xfer->watcher = 0;
-		fclose(xfer->file);
-		xfer->file = 0;
-		ft_nextfile(xfer);
+		if (stat(filename, &sb) == -1) {
+			char *msg;
+
+			msg = g_strdup_printf(_("%s was not found.\n"), filename);
+
+			gaim_xfer_error(type, xfer->who, msg);
+
+			g_free(msg);
+			g_free(filename);
+
+			return;
+		}
+
+		gaim_xfer_set_size(xfer, sb.st_size);
+	}
+	else {
+		/* TODO: Sanity checks and file overwrite checks. */
+
+		gaim_xfer_set_dest_filename(xfer, filename);
 	}
 
-	if (buf != NULL)
-		g_free(buf);
+	g_free(filename);
+
+	xfer->ops.init(xfer);
+}
+
+void
+gaim_xfer_request_denied(struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return;
+
+	/* TODO */
+}
+
+GaimXferType
+gaim_xfer_get_type(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return GAIM_XFER_UNKNOWN;
+
+	return xfer->type;
+}
+
+struct gaim_account *
+gaim_xfer_get_account(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->account;
+}
+
+const char *
+gaim_xfer_get_filename(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->filename;
+}
+
+const char *
+gaim_xfer_get_dest_filename(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->dest_filename;
+}
+
+size_t
+gaim_xfer_get_bytes_sent(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return 0;
+
+	return xfer->bytes_sent;
+}
+
+size_t
+gaim_xfer_get_bytes_remaining(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return 0;
+
+	return xfer->bytes_remaining;
+}
+
+size_t
+gaim_xfer_get_size(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return 0;
+
+	return xfer->size;
+}
+
+double
+gaim_xfer_get_progress(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return 0.0;
+
+	return ((double)gaim_xfer_get_bytes_sent(xfer) /
+			(double)gaim_xfer_get_size(xfer));
+}
+
+const char *
+gaim_xfer_get_local_ip(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->local_ip;
+}
+
+unsigned int
+gaim_xfer_get_local_port(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return -1;
+
+	return xfer->local_port;
+}
+
+const char *
+gaim_xfer_get_remote_ip(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->remote_ip;
+}
+
+unsigned int
+gaim_xfer_get_remote_port(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return -1;
+
+	return xfer->remote_port;
+}
+
+void
+gaim_xfer_set_filename(struct gaim_xfer *xfer, const char *filename)
+{
+	if (xfer == NULL)
+		return;
+
+	if (xfer->filename != NULL)
+		g_free(xfer->filename);
+
+	xfer->filename = (filename == NULL ? NULL : g_strdup(filename));
+}
+
+void
+gaim_xfer_set_dest_filename(struct gaim_xfer *xfer, const char *filename)
+{
+	if (xfer == NULL)
+		return;
+
+	if (xfer->dest_filename != NULL)
+		g_free(xfer->dest_filename);
+
+	xfer->dest_filename = (filename == NULL ? NULL : g_strdup(filename));
+}
+
+void
+gaim_xfer_set_size(struct gaim_xfer *xfer, size_t size)
+{
+	if (xfer == NULL || size == 0)
+		return;
+
+	xfer->size = size;
+}
+
+struct gaim_xfer_ui_ops *
+gaim_xfer_get_ui_ops(const struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return NULL;
+
+	return xfer->ui_ops;
+}
+
+void
+gaim_xfer_set_init_fnc(struct gaim_xfer *xfer,
+					   void (*fnc)(struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.init = fnc;
+}
+
+
+void
+gaim_xfer_set_read_fnc(struct gaim_xfer *xfer,
+					   size_t (*fnc)(char **, const struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.read = fnc;
+}
+
+void
+gaim_xfer_set_write_fnc(struct gaim_xfer *xfer,
+						size_t (*fnc)(const char *, size_t,
+									  const struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.write = fnc;
+}
+
+void
+gaim_xfer_set_ack_fnc(struct gaim_xfer *xfer,
+					  void (*fnc)(struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.ack = fnc;
+}
+
+void
+gaim_xfer_set_start_fnc(struct gaim_xfer *xfer,
+						void (*fnc)(struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.start = fnc;
+}
+
+void
+gaim_xfer_set_end_fnc(struct gaim_xfer *xfer,
+					  void (*fnc)(struct gaim_xfer *))
+{
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.end = fnc;
 }
 
-static void ft_nextfile(struct file_transfer *xfer)
+void
+gaim_xfer_set_cancel_fnc(struct gaim_xfer *xfer,
+						 void (*fnc)(struct gaim_xfer *))
 {
-	debug_printf("file transfer %d of %d done\n",
-			xfer->filesdone + 1, xfer->totfiles);
+	if (xfer == NULL)
+		return;
+
+	xfer->ops.cancel = fnc;
+}
+
+size_t
+gaim_xfer_read(struct gaim_xfer *xfer, char **buffer)
+{
+	size_t s, r;
 
-	if (++xfer->filesdone == xfer->totfiles) {
-		char *msg;
-		char *msg2;
+	if (xfer == NULL || buffer == NULL)
+		return 0;
+
+	s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096);
 
-		xfer->gc->prpl->file_transfer_done(xfer->gc, xfer);
+	if (xfer->ops.read != NULL)
+		r = xfer->ops.read(buffer, xfer);
+	else {
+		*buffer = g_malloc0(s);
+
+		r = read(xfer->fd, *buffer, s);
+	}
+
+	return r;
+}
 
-		if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE)
-			msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."),
-					xfer->who, xfer->gc->username);
-		else 
-			msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."),
-					xfer->gc->username, xfer->who);
-		xfer->state = FILE_TRANSFER_STATE_DONE;
+size_t
+gaim_xfer_write(struct gaim_xfer *xfer, const char *buffer, size_t size)
+{
+	size_t r, s;
+
+	if (xfer == NULL || buffer == NULL || size == 0)
+		return 0;
+
+	s = MIN(gaim_xfer_get_bytes_remaining(xfer), size);
+
+	if (xfer->ops.write != NULL)
+		r = xfer->ops.write(buffer, s, xfer);
+	else
+		r = write(xfer->fd, buffer, s);
 
-		if (xfer->totfiles > 1)
-			msg2 = g_strdup_printf(_("%d files transferred."), 
-						xfer->totfiles);
-		else
-			msg2 = NULL;
+	return r;
+}
 
-		do_error_dialog(msg, msg2, GAIM_INFO);
-		g_free(msg);
-		if (msg2)
-			g_free(msg2);
+static void
+transfer_cb(gpointer data, gint source, GaimInputCondition condition)
+{
+	struct gaim_xfer_ui_ops *ui_ops;
+	struct gaim_xfer *xfer = (struct gaim_xfer *)data;
+	char *buffer = NULL;
+	size_t r;
 
-		ft_delete(xfer);
+	if (condition & GAIM_INPUT_READ) {
+		r = gaim_xfer_read(xfer, &buffer);
+
+		if (r > 0)
+			fwrite(buffer, 1, r, xfer->dest_fp);
 	}
 	else {
-		xfer->gc->prpl->file_transfer_nextfile(xfer->gc, xfer);
+		size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096);
+
+		buffer = g_malloc0(s);
+
+		fread(buffer, 1, s, xfer->dest_fp);
+
+		/* Write as much as we're allowed to. */
+		r = gaim_xfer_write(xfer, buffer, s);
+
+		if (r < s) {
+			/* We have to seek back in the file now. */
+			fseek(xfer->dest_fp, r - s, SEEK_CUR);
+		}
 	}
+
+	g_free(buffer);
+
+	if (r < 0)
+		return;
+
+	xfer->bytes_remaining -= r;
+	xfer->bytes_sent += r;
+
+	if (xfer->ops.ack != NULL)
+		xfer->ops.ack(xfer);
+
+	ui_ops = gaim_xfer_get_ui_ops(xfer);
+
+	if (ui_ops != NULL && ui_ops->update_progress != NULL)
+		ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer));
+
+	if (xfer->bytes_remaining == 0)
+		gaim_xfer_end(xfer);
+}
+
+static void
+begin_transfer(struct gaim_xfer *xfer, GaimInputCondition cond)
+{
+	struct gaim_xfer_ui_ops *ui_ops;
+	GaimXferType type = gaim_xfer_get_type(xfer);
+
+	ui_ops = gaim_xfer_get_ui_ops(xfer);
+
+	/*
+	 * We'll have already tried to open this earlier to make sure we can
+	 * read/write here. Should be safe.
+	 */
+	xfer->dest_fp = fopen(gaim_xfer_get_dest_filename(xfer),
+						  type == GAIM_XFER_RECEIVE ? "wb" : "rb");
+
+	/* Just in case, though. */
+	if (xfer->dest_fp == NULL) {
+		gaim_xfer_cancel(xfer); /* ? */
+		return;
+	}
+
+	xfer->watcher = gaim_input_add(xfer->fd, cond, transfer_cb, xfer);
+
+	if (ui_ops != NULL && ui_ops->add_xfer != NULL)
+		ui_ops->add_xfer(xfer);
+
+	if (xfer->ops.start != NULL)
+		xfer->ops.start(xfer);
+}
+
+static void
+connect_cb(gpointer data, gint source, GaimInputCondition condition)
+{
+	struct gaim_xfer *xfer = (struct gaim_xfer *)data;
+
+	xfer->fd = source;
+
+	begin_transfer(xfer, condition);
 }
 
+void
+gaim_xfer_start(struct gaim_xfer *xfer, int fd, const char *ip,
+				unsigned int port)
+{
+	GaimInputCondition cond;
+	GaimXferType type;
+
+	if (xfer == NULL || gaim_xfer_get_type(xfer) == GAIM_XFER_UNKNOWN)
+		return;
+
+	type = gaim_xfer_get_type(xfer);
+
+	xfer->bytes_remaining = gaim_xfer_get_size(xfer);
+	xfer->bytes_sent      = 0;
+
+	if (type == GAIM_XFER_RECEIVE) {
+		cond = GAIM_INPUT_READ;
+
+		if (ip != NULL) {
+			xfer->remote_ip   = g_strdup(ip);
+			xfer->remote_port = port;
+
+			/* Establish a file descriptor. */
+			proxy_connect(xfer->remote_ip, xfer->remote_port,
+						  connect_cb, xfer);
+
+			return;
+		}
+		else {
+			xfer->fd = fd;
+		}
+	}
+	else {
+		cond = GAIM_INPUT_WRITE;
+
+		xfer->fd = fd;
+	}
+
+	begin_transfer(xfer, cond);
+}
+
+void
+gaim_xfer_end(struct gaim_xfer *xfer)
+{
+	if (xfer == NULL)
+		return;
+
+	/* See if we are actually trying to cancel this. */
+	if (gaim_xfer_get_bytes_remaining(xfer) > 0) {
+		gaim_xfer_cancel(xfer);
+		return;
+	}
+
+	if (xfer->ops.end != NULL)
+		xfer->ops.end(xfer);
+
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = 0;
+
+	close(xfer->fd);
+
+	if (xfer->dest_fp != NULL) {
+		fclose(xfer->dest_fp);
+		xfer->dest_fp = NULL;
+	}
+
+	/* Delete the transfer. */
+	gaim_xfer_destroy(xfer);
+}
+
+void
+gaim_xfer_cancel(struct gaim_xfer *xfer)
+{
+	struct gaim_xfer_ui_ops *ui_ops;
+
+	if (xfer == NULL)
+		return;
+
+	if (xfer->ops.cancel != NULL)
+		xfer->ops.cancel(xfer);
+
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = 0;
+
+	close(xfer->fd);
+
+	if (xfer->dest_fp != NULL) {
+		fclose(xfer->dest_fp);
+		xfer->dest_fp = NULL;
+	}
+
+	ui_ops = gaim_xfer_get_ui_ops(xfer);
+
+	if (ui_ops != NULL && ui_ops->cancel != NULL)
+		ui_ops->cancel(xfer);
+
+	/* Delete the transfer. */
+	gaim_xfer_destroy(xfer);
+}
+
+void
+gaim_xfer_error(GaimXferType type, const char *who, const char *msg)
+{
+	char *title;
+
+	if (xfer == NULL || msg == NULL || type == GAIM_XFER_UNKNOWN)
+		return;
+
+	if (type == GAIM_XFER_SEND)
+		title = g_strdup_printf(_("File transfer to %s aborted.\n"), who);
+	else
+		title = g_strdup_printf(_("File transfer from %s aborted.\n"), who);
+
+	do_error_dialog(title, msg, GAIM_ERROR);
+
+	g_free(title);
+}
+
+void
+gaim_set_xfer_ui_ops(struct gaim_xfer_ui_ops *ops)
+{
+	if (ops == NULL)
+		return;
+
+	xfer_ui_ops = ops;
+}
+
+struct gaim_xfer_ui_ops *
+gaim_get_xfer_ui_ops(void)
+{
+	return xfer_ui_ops;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ft.h	Tue Feb 04 06:57:35 2003 +0000
@@ -0,0 +1,450 @@
+/**
+ * @file ft.h The file transfer interface
+ *
+ * gaim
+ *
+ * Copyright (C) 2002-2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * 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.
+ *
+ * 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
+ *
+ */
+#ifndef _GAIM_FT_H_
+#define _GAIM_FT_H_
+
+/**************************************************************************/
+/** Data Structures                                                       */
+/**************************************************************************/
+struct gaim_xfer *xfer;
+
+/**
+ * Types of file transfers.
+ */
+typedef enum
+{
+	GAIM_XFER_UNKNOWN = 0,  /**< Unknown file transfer type. */
+	GAIM_XFER_SEND,         /**< File sending.               */
+	GAIM_XFER_RECEIVE       /**< File receiving.             */
+
+} GaimXferType;
+
+/**
+ * File transfer UI operations.
+ *
+ * Any UI representing a file transfer must assign a filled-out
+ * gaim_xfer_ui_ops structure to the gaim_xfer.
+ */
+struct gaim_xfer_ui_ops
+{
+	void (*destroy)(struct gaim_xfer *xfer);
+	void (*request_file)(struct gaim_xfer *xfer);
+	void (*ask_cancel)(struct gaim_xfer *xfer);
+	void (*add_xfer)(struct gaim_xfer *xfer);
+	void (*update_progress)(struct gaim_xfer *xfer, double percent);
+	void (*cancel)(struct gaim_xfer *xfer);
+};
+
+/**
+ * A core representation of a file transfer.
+ */
+struct gaim_xfer
+{
+	GaimXferType type;            /**< The type of transfer.               */
+
+	struct gaim_account *account; /**< The account.                        */
+
+	char *who;                    /**< The person on the other end of the
+	                                   transfer.                           */
+
+	char *filename;               /**< The name of the file.               */
+	char *dest_filename;          /**< The destination filename.           */
+	size_t size;                  /**< The size of the file.               */
+
+	FILE *dest_fp;                /**< The destination file pointer.       */
+
+	char *local_ip;               /**< The local IP address.               */
+	char *remote_ip;              /**< The remote IP address.              */
+	int local_port;               /**< The local port.                     */
+	int remote_port;              /**< The remote port.                    */
+
+	int fd;                       /**< The socket file descriptor.         */
+	int watcher;                  /**< Watcher.                            */
+
+	size_t bytes_sent;            /**< The number of bytes sent.           */
+	size_t bytes_remaining;       /**< The number of bytes remaining.      */
+
+	/* I/O operations. */
+	struct
+	{
+		void (*init)(struct gaim_xfer *xfer);
+		void (*start)(struct gaim_xfer *xfer);
+		void (*end)(struct gaim_xfer *xfer);
+		void (*cancel)(struct gaim_xfer *xfer);
+		size_t (*read)(char **buffer, const struct gaim_xfer *xfer);
+		size_t (*write)(const char *buffer, size_t size,
+						const struct gaim_xfer *xfer);
+		void (*ack)(struct gaim_xfer *xfer);
+
+	} ops;
+
+	struct gaim_xfer_ui_ops *ui_ops;  /**< UI-specific operations. */
+	void *ui_data;                    /**< UI-specific data.       */
+
+	void *data;                       /**< prpl-specific data.     */
+};
+
+/**************************************************************************/
+/** @name File Transfer API                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new file transfer handle.
+ *
+ * @param account The account sending or receiving the file.
+ * @param type    The type of file transfer.
+ * @param who     The name of the remote user.
+ *
+ * @return A file transfer handle.
+ */
+struct gaim_xfer *gaim_xfer_new(struct gaim_account *account,
+								GaimXferType type, const char *who);
+
+/**
+ * Destroys a file transfer handle.
+ *
+ * @param xfer The file transfer to destroy.
+ */
+void gaim_xfer_destroy(struct gaim_xfer *xfer);
+
+/**
+ * Requests confirmation for a file transfer from the user.
+ *
+ * @param xfer The file transfer to request confirmation on.
+ */
+void gaim_xfer_request(struct gaim_xfer *xfer);
+
+/**
+ * Called if the user accepts the file transfer request.
+ *
+ * @param xfer     The file transfer.
+ * @param filename The filename.
+ */
+void gaim_xfer_request_accepted(struct gaim_xfer *xfer, char *filename);
+
+/**
+ * Called if the user rejects the file transfer request.
+ *
+ * @param xfer The file transfer.
+ */
+void gaim_xfer_request_denied(struct gaim_xfer *xfer);
+
+/**
+ * Returns the type of file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The type of the file transfer.
+ */
+GaimXferType gaim_xfer_get_type(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the account the file transfer is using.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The account.
+ */
+struct gaim_account *gaim_xfer_get_account(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the name of the file being sent or received.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The filename.
+ */
+const char *gaim_xfer_get_filename(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the file's destination filename,
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The destination filename.
+ */
+const char *gaim_xfer_get_dest_filename(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the number of bytes sent so far.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The number of bytes sent.
+ */
+size_t gaim_xfer_get_bytes_sent(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the number of bytes received so far.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The number of bytes received.
+ */
+size_t gaim_xfer_get_bytes_remaining(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the size of the file being sent or received.
+ *
+ * @param xfer The file transfer.
+ * 
+ * @return The total size of the file.
+ */
+size_t gaim_xfer_get_size(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the current percentage of progress of the transfer.
+ *
+ * This is a number between 0 (0%) and 1 (100%).
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The percentage complete.
+ */
+double gaim_xfer_get_progress(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the local IP address in the file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The IP address on this end.
+ */
+const char *gaim_xfer_get_local_ip(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the local port number in the file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The port number on this end.
+ */
+unsigned int gaim_xfer_get_local_port(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the remote IP address in the file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The IP address on the other end.
+ */
+const char *gaim_xfer_get_remote_ip(const struct gaim_xfer *xfer);
+
+/**
+ * Returns the remote port number in the file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The port number on the other end.
+ */
+unsigned int gaim_xfer_get_remote_port(const struct gaim_xfer *xfer);
+
+/**
+ * Sets the filename for the file transfer.
+ *
+ * @param xfer     The file transfer.
+ * @param filename The filename.
+ */
+void gaim_xfer_set_filename(struct gaim_xfer *xfer, const char *filename);
+
+/**
+ * Sets the destination filename for the file transfer.
+ *
+ * @param xfer     The file transfer.
+ * @param filename The filename
+ */
+void gaim_xfer_set_dest_filename(struct gaim_xfer *xfer, const char *filename);
+
+/**
+ * Sets the size of the file in a file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param size The size of the file.
+ */
+void gaim_xfer_set_size(struct gaim_xfer *xfer, size_t size);
+
+/**
+ * Returns the UI operations structure for a file transfer.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The UI operations structure.
+ */
+struct gaim_xfer_ui_ops *gaim_xfer_get_ui_ops(const struct gaim_xfer *xfer);
+
+/**
+ * Sets the read function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The read function.
+ */
+void gaim_xfer_set_read_fnc(struct gaim_xfer *xfer,
+	size_t (*fnc)(char **, const struct gaim_xfer *));
+
+/**
+ * Sets the write function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The write function.
+ */
+void gaim_xfer_set_write_fnc(struct gaim_xfer *xfer,
+		size_t (*fnc)(const char *, size_t, const struct gaim_xfer *));
+
+/**
+ * Sets the acknowledge function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The acknowledge function.
+ */
+void gaim_xfer_set_ack_fnc(struct gaim_xfer *xfer,
+						   void (*fnc)(struct gaim_xfer *));
+
+/**
+ * Sets the transfer initialization function for the file transfer.
+ *
+ * This function is required, and must call gaim_xfer_start() with
+ * the necessary parameters. This will be called if the file transfer
+ * is accepted by the user.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The transfer initialization function.
+ */
+void gaim_xfer_set_init_fnc(struct gaim_xfer *xfer,
+							void (*fnc)(struct gaim_xfer *));
+
+/**
+ * Sets the start transfer function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The start transfer function.
+ */
+void gaim_xfer_set_start_fnc(struct gaim_xfer *xfer,
+							 void (*fnc)(struct gaim_xfer *));
+
+/**
+ * Sets the end transfer function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The end transfer function.
+ */
+void gaim_xfer_set_end_fnc(struct gaim_xfer *xfer,
+						   void (*fnc)(struct gaim_xfer *));
+
+/**
+ * Sets the cancel transfer function for the file transfer.
+ *
+ * @param xfer The file transfer.
+ * @param fnc  The cancel transfer function.
+ */
+void gaim_xfer_set_cancel_fnc(struct gaim_xfer *xfer,
+							  void (*fnc)(struct gaim_xfer *));
+
+/**
+ * Reads in data from a file transfer stream.
+ *
+ * @param xfer   The file transfer.
+ * @param buffer The buffer that will be created to contain the data.
+ *
+ * @return The number of bytes read.
+ */
+size_t gaim_xfer_read(struct gaim_xfer *xfer, char **buffer);
+
+/**
+ * Writes data to a file transfer stream.
+ *
+ * @param xfer   The file transfer.
+ * @param buffer The buffer to read the data from.
+ * @param size   The number of bytes to write.
+ *
+ * @return The number of bytes written.
+ */
+size_t gaim_xfer_write(struct gaim_xfer *xfer, const char *buffer,
+					   size_t size);
+
+/**
+ * Starts a file transfer.
+ *
+ * Either @a fd must be specified <i>or</i> @a ip and @a port on a
+ * file receive transfer. On send, @a fd must be specified, and
+ * @a ip and @a port are ignored.
+ *
+ * @param xfer The file transfer.
+ * @param fd   The file descriptor for the socket.
+ * @param ip   The IP address to connect to.
+ * @param port The port to connect to.
+ */
+void gaim_xfer_start(struct gaim_xfer *xfer, int fd, const char *ip,
+					 unsigned int port);
+
+/**
+ * Ends a file transfer.
+ *
+ * @param xfer The file transfer.
+ */
+void gaim_xfer_end(struct gaim_xfer *xfer);
+
+/**
+ * Cancels a file transfer.
+ *
+ * @param xfer The file transfer.
+ */
+void gaim_xfer_cancel(struct gaim_xfer *xfer);
+
+/**
+ * Displays a file transfer-related error message.
+ *
+ * This is a wrapper around do_error_dialog(), which automatically
+ * specifies a title ("File transfer to <i>user</i> aborted" or
+ * "File Transfer from <i>user</i> aborted").
+ *
+ * @param type The type of file transfer.
+ * @param who  The user on the other end of the transfer.
+ * @param msg  The message to display.
+ */
+void gaim_xfer_error(GaimXferType type, const char *who, const char *msg);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name UI Registration Functions                                       */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Sets the UI operations structure to be used in all gaim file transfers.
+ *
+ * @param fnc The function.
+ */
+void gaim_set_xfer_ui_ops(struct gaim_xfer_ui_ops *ops);
+
+/**
+ * Returns the UI operations structure to be used in all gaim file transfers.
+ *
+ * @return The UI operations structure.
+ */
+struct gaim_xfer_ui_ops *gaim_get_xfer_ui_ops(void);
+
+/*@}*/
+
+#endif /* _GAIM_FT_H_ */
--- a/src/gtkcellrendererprogress.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/gtkcellrendererprogress.c	Tue Feb 04 06:57:35 2003 +0000
@@ -47,6 +47,7 @@
 						   GdkRectangle               *cell_area,
 						   GdkRectangle               *expose_area,
 						   guint                       flags);
+#if 0
 static gboolean gtk_cell_renderer_progress_activate  (GtkCellRenderer            *cell,
 						      GdkEvent                   *event,
 						      GtkWidget                  *widget,
@@ -54,6 +55,7 @@
 						      GdkRectangle               *background_area,
 						      GdkRectangle               *cell_area,
 						      guint                       flags);
+#endif
 static void  gtk_cell_renderer_progress_finalize (GObject *gobject);
 
 enum {
@@ -69,7 +71,7 @@
 };
      
 static gpointer parent_class;
-static guint progress_cell_renderer_signals [LAST_SIGNAL];
+/* static guint progress_cell_renderer_signals [LAST_SIGNAL]; */
 
 GType  gtk_cell_renderer_progress_get_type (void)
 {
@@ -148,7 +150,9 @@
 
 static void gtk_cell_renderer_progress_finalize (GObject *object)
 {
+/*
 	GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS(object);
+*/
 
 	(* G_OBJECT_CLASS (parent_class)->finalize) (object);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkft.c	Tue Feb 04 06:57:35 2003 +0000
@@ -0,0 +1,610 @@
+/**
+ * @file gtkft.c The GTK+ file transfer UI
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * 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.
+ *
+ * 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
+ *
+ */
+#include "gaim.h"
+#include "prpl.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include "gtkcellrendererprogress.h"
+
+struct gaim_gtkxfer_dialog
+{
+	GtkWidget *window;
+	GtkWidget *tree;
+	GtkListStore *model;
+};
+
+struct gaim_gtkxfer_ui_data
+{
+	GtkWidget *filesel;
+	GtkTreeIter iter;
+	time_t start_time;
+
+	char *name;
+};
+
+static struct gaim_gtkxfer_dialog *xfer_dialog = NULL;
+
+enum
+{
+	COLUMN_STATUS = 0,
+	COLUMN_USER,
+	COLUMN_FILENAME,
+	COLUMN_PROGRESS,
+	COLUMN_SIZE,
+	COLUMN_REMAINING,
+	COLUMN_ESTIMATE,
+	COLUMN_SPEED,
+	NUM_COLUMNS
+};
+
+static gint
+delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
+{
+	gaim_gtkxfer_dialog_hide();
+
+	return TRUE;
+}
+
+static void
+close_win_cb(GtkButton *button, gpointer user_data)
+{
+	gaim_gtkxfer_dialog_hide();
+}
+
+static struct gaim_gtkxfer_dialog *
+build_xfer_dialog(void)
+{
+	struct gaim_gtkxfer_dialog *dialog;
+	GtkWidget *window;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *frame;
+	GtkWidget *sw;
+	GtkWidget *sep;
+	GtkWidget *button;
+	GtkWidget *tree;
+	GtkTreeViewColumn *column;
+	GtkListStore *model;
+	GtkCellRenderer *renderer;
+	GtkSizeGroup *sg;
+
+	dialog = g_malloc0(sizeof(struct gaim_gtkxfer_dialog));
+
+	/* Create the window. */
+	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_role(GTK_WINDOW(window), "file transfer");
+	gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
+	gtk_window_set_default_size(GTK_WINDOW(window), 550, 250);
+	gtk_container_border_width(GTK_CONTAINER(window), 0);
+	gtk_widget_realize(window);
+
+	g_signal_connect(G_OBJECT(window), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Create the parent vbox for everything. */
+	vbox = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_container_add(GTK_CONTAINER(window), vbox);
+	gtk_widget_show(vbox);
+
+	/* Create the scrolled window. */
+	sw = gtk_scrolled_window_new(0, 0);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_AUTOMATIC,
+								   GTK_POLICY_ALWAYS);
+	gtk_widget_show(sw);
+
+	/* Build the tree model */
+	/* Transfer type, User, Filename, Progress Bar, Size, Remaining */
+	dialog->model = model = gtk_list_store_new(NUM_COLUMNS,
+											   GDK_TYPE_PIXBUF, G_TYPE_STRING,
+											   G_TYPE_STRING, G_TYPE_DOUBLE,
+											   G_TYPE_LONG, G_TYPE_LONG,
+											   G_TYPE_STRING, G_TYPE_STRING);
+
+	/* Create the treeview */
+	dialog->tree = tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
+	gtk_tree_selection_set_mode(
+			gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
+			GTK_SELECTION_MULTIPLE);
+	gtk_widget_show(tree);
+
+	g_object_unref(G_OBJECT(model));
+
+
+	/* Columns */
+
+	/* Transfer Type column */
+	renderer = gtk_cell_renderer_pixbuf_new();
+	column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
+				"pixbuf", COLUMN_STATUS, NULL);
+	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
+									GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 25);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* User column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("User"), renderer,
+				"text", COLUMN_USER, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* Filename column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer,
+				"text", COLUMN_FILENAME, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* Progress bar column */
+	renderer = gtk_cell_renderer_progress_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Progress"), renderer,
+				"percentage", COLUMN_PROGRESS, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* File Size column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer,
+				"text", COLUMN_SIZE, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* Bytes Remaining column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Remaining"),
+				renderer, "text", COLUMN_REMAINING, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));
+
+	/* ETA column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("ETA"),
+				renderer, "text", COLUMN_ESTIMATE, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	/* Speed column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Speed"),
+				renderer, "text", COLUMN_SPEED, NULL);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));
+
+	gtk_container_add(GTK_CONTAINER(sw), tree);
+	gtk_widget_show(tree);
+
+	/* Create the outer frame for the scrolled window. */
+	frame = gtk_frame_new(NULL),
+	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(frame), sw);
+	gtk_widget_show(frame);
+
+	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+
+	/* Separator */
+	sep = gtk_hseparator_new();
+	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+	gtk_widget_show(sep);
+
+	/* Now the hbox for the buttons */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
+	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
+
+	/* Close button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(close_win_cb), NULL);
+	gtk_size_group_add_widget(sg, button);
+	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+#if 0
+	/* Cancel button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+	gtk_size_group_add_widget(sg, button);
+	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+#endif
+
+	return dialog;
+}
+
+static void
+gaim_gtkxfer_destroy(struct gaim_xfer *xfer)
+{
+	struct gaim_gtkxfer_ui_data *data;
+
+	data = xfer->ui_data;
+
+	if (data == NULL)
+		return;
+}
+
+static gboolean
+choose_file_close_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+	gaim_xfer_request_denied((struct gaim_xfer *)user_data);
+
+	return FALSE;
+}
+
+static void
+choose_file_cancel_cb(GtkButton *button, gpointer user_data)
+{
+	struct gaim_xfer *xfer = (struct gaim_xfer *)user_data;
+	struct gaim_gtkxfer_ui_data *data;
+
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	gaim_xfer_request_denied(xfer);
+
+	gtk_widget_destroy(data->filesel);
+	data->filesel = NULL;
+}
+
+static int
+do_overwrite_cb(struct gaim_xfer *xfer)
+{
+	struct gaim_gtkxfer_ui_data *data;
+	
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	gaim_xfer_request_accepted(xfer, data->name);
+
+	/*
+	 * No, we don't want to free data->name. gaim_xfer_request_accepted
+	 * will deal with it.
+	 */
+	data->name = NULL;
+
+	return 0;
+}
+
+static int
+dont_overwrite_cb(struct gaim_xfer *xfer)
+{
+	struct gaim_gtkxfer_ui_data *data;
+	
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	g_free(data->name);
+	data->name = NULL;
+
+	gaim_xfer_request_denied(xfer);
+
+	return 0;
+}
+
+static void
+choose_file_ok_cb(GtkButton *button, gpointer user_data)
+{
+	struct gaim_xfer *xfer;
+	struct gaim_gtkxfer_ui_data *data;
+	struct stat st;
+	const char *name;
+
+	xfer = (struct gaim_xfer *)user_data;
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(data->filesel));
+
+	if (stat(name, &st) == 0) {
+		if (S_ISDIR(st.st_mode)) {
+			/* XXX */
+			gaim_xfer_request_denied(xfer);
+		}
+		else if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
+			data->name = g_strdup(name);
+
+			do_ask_dialog(_("That file already exists. "
+							"Would you like to overwrite it?"),
+						  NULL, xfer,
+						  _("Yes"), do_overwrite_cb,
+						  _("No"), dont_overwrite_cb,
+						  NULL, FALSE);
+		}
+		else {
+			gaim_xfer_request_accepted(xfer, g_strdup(name));
+		}
+	}
+	else {
+		/* File not found. */
+		if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
+			gaim_xfer_request_accepted(xfer, g_strdup(name));
+		}
+		else {
+			do_error_dialog(_("That file does not exist."),
+							NULL, GAIM_ERROR);
+
+			gaim_xfer_request_denied(xfer);
+		}
+	}
+
+	gtk_widget_destroy(data->filesel);
+	data->filesel = NULL;
+}
+
+static int
+choose_file(struct gaim_xfer *xfer)
+{
+	char *cur_dir, *init_str;
+	struct gaim_gtkxfer_ui_data *data;
+
+	cur_dir = g_get_current_dir();
+
+	/* This is where we're setting xfer->ui_data for the first time. */
+	data = g_malloc0(sizeof(struct gaim_gtkxfer_ui_data));
+	xfer->ui_data = data;
+
+	if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND)
+		data->filesel = gtk_file_selection_new(_("Gaim - Open..."));
+	else
+		data->filesel = gtk_file_selection_new(_("Gaim - Save As..."));
+
+	if (gaim_xfer_get_filename(xfer) == NULL)
+		init_str = g_strdup(cur_dir);
+	else
+		init_str = g_build_filename(cur_dir, gaim_xfer_get_filename(xfer),
+									NULL);
+
+	g_free(cur_dir);
+
+	gtk_file_selection_set_filename(GTK_FILE_SELECTION(data->filesel),
+									init_str);
+
+	g_free(init_str);
+
+	g_signal_connect(G_OBJECT(data->filesel), "delete_event",
+					 G_CALLBACK(choose_file_close_cb), xfer);
+	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->cancel_button),
+					 "clicked",
+					 G_CALLBACK(choose_file_cancel_cb), xfer);
+	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->ok_button),
+					 "clicked",
+					 G_CALLBACK(choose_file_ok_cb), xfer);
+
+	gtk_widget_show(data->filesel);
+
+	return 0;
+}
+
+static int
+cancel_recv_cb(struct gaim_xfer *xfer)
+{
+	gaim_xfer_request_denied(xfer);
+
+	return 0;
+}
+
+static void
+gaim_gtkxfer_ask_recv(struct gaim_xfer *xfer)
+{
+	static const char *size_str[4] = { "bytes", "KB", "MB", "GB" };
+	char *buf;
+	char *size_buf;
+	float size_mag;
+	size_t size;
+	int size_index = 0;
+
+	size = gaim_xfer_get_size(xfer);
+	size_mag = (float)size;
+
+	while ((size_index < 4) && (size_mag > 1024)) {
+		size_mag /= 1024;
+		size_index++;
+	}
+
+	if (size == -1)
+		size_buf = g_strdup_printf(_("Unknown size"));
+	else
+		size_buf = g_strdup_printf("%.3g %s", size_mag, size_str[size_index]);
+
+	buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
+						  xfer->who, gaim_xfer_get_filename(xfer), size_buf);
+
+	g_free(size_buf);
+
+	do_ask_dialog(buf, NULL, xfer,
+				  _("Accept"), choose_file,
+				  _("Cancel"), cancel_recv_cb,
+				  NULL, FALSE);
+
+	g_free(buf);
+}
+
+static void
+gaim_gtkxfer_request_file(struct gaim_xfer *xfer)
+{
+	if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE)
+	{
+		gaim_gtkxfer_ask_recv(xfer);
+	}
+	else
+	{
+		choose_file(xfer);
+	}
+}
+
+static void
+gaim_gtkxfer_ask_cancel(struct gaim_xfer *xfer)
+{
+}
+
+static void
+gaim_gtkxfer_add_xfer(struct gaim_xfer *xfer)
+{
+	struct gaim_gtkxfer_ui_data *data;
+	GaimXferType type;
+	GdkPixbuf *pixbuf;
+
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	gaim_gtkxfer_dialog_show();
+
+	data->start_time = time(NULL);
+
+	type = gaim_xfer_get_type(xfer);
+
+	pixbuf = gtk_widget_render_icon(xfer_dialog->window,
+									(type == GAIM_XFER_RECEIVE
+									 ? GAIM_STOCK_DOWNLOAD
+									 : GAIM_STOCK_UPLOAD),
+									GTK_ICON_SIZE_MENU, NULL);
+
+	gtk_list_store_append(xfer_dialog->model, &data->iter);
+	gtk_list_store_set(xfer_dialog->model, &data->iter,
+					   COLUMN_STATUS, pixbuf,
+					   COLUMN_USER, xfer->who,
+					   COLUMN_FILENAME, gaim_xfer_get_filename(xfer),
+					   COLUMN_PROGRESS, 0.0,
+					   COLUMN_SIZE, gaim_xfer_get_size(xfer),
+					   COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
+					   COLUMN_ESTIMATE, NULL,
+					   COLUMN_SPEED, NULL,
+					   -1);
+
+	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(xfer_dialog->tree));
+
+	g_object_unref(pixbuf);
+}
+
+static void
+gaim_gtkxfer_update_progress(struct gaim_xfer *xfer, double percent)
+{
+	struct gaim_gtkxfer_ui_data *data;
+	time_t now;
+	char speed_buf[256];
+	char estimate_buf[256];
+	double kb_sent, kb_rem;
+	double kbps = 0.0;
+	time_t elapsed;
+	int secs_remaining;
+
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	now     = time(NULL);
+	kb_rem  = gaim_xfer_get_bytes_remaining(xfer) / 1024.0;
+	kb_sent = gaim_xfer_get_bytes_sent(xfer) / 1024.0;
+	elapsed = (now - data->start_time);
+	kbps    = (elapsed > 0 ? (kb_sent / elapsed) : 0);
+
+	secs_remaining = (int)(kb_rem / kbps);
+
+	if (secs_remaining <= 0) {
+		GdkPixbuf *pixbuf = NULL;
+
+		*speed_buf = '\0';
+		strncpy(estimate_buf, _("Done."), sizeof(estimate_buf));
+
+		pixbuf = gtk_widget_render_icon(xfer_dialog->window,
+										GAIM_STOCK_FILE_DONE,
+										GTK_ICON_SIZE_MENU, NULL);
+
+		gtk_list_store_set(xfer_dialog->model, &data->iter,
+						   COLUMN_STATUS, pixbuf,
+						   -1);
+
+		g_object_unref(pixbuf);
+	}
+	else {
+		int h = secs_remaining / 3600;
+		int m = (secs_remaining % 3600) / 60;
+		int s = secs_remaining % 60;
+
+		g_snprintf(estimate_buf, sizeof(estimate_buf),
+				   _("%d:%02d:%02d"), h, m, s);
+		g_snprintf(speed_buf, sizeof(speed_buf),
+				   _("%.2f KB/s"), kbps);
+	}
+
+	gtk_list_store_set(xfer_dialog->model, &data->iter,
+					   COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
+					   COLUMN_PROGRESS, percent,
+					   COLUMN_ESTIMATE, estimate_buf,
+					   COLUMN_SPEED, speed_buf,
+					   -1);
+}
+
+static void
+gaim_gtkxfer_cancel(struct gaim_xfer *xfer)
+{
+	struct gaim_gtkxfer_ui_data *data;
+	GdkPixbuf *pixbuf;
+
+	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
+
+	pixbuf = gtk_widget_render_icon(xfer_dialog->window,
+									GAIM_STOCK_FILE_CANCELED,
+									GTK_ICON_SIZE_MENU, NULL);
+
+	gtk_list_store_set(xfer_dialog->model, &data->iter,
+					   COLUMN_STATUS, pixbuf,
+					   -1);
+
+	g_object_unref(pixbuf);
+}
+
+struct gaim_xfer_ui_ops ops =
+{
+	gaim_gtkxfer_destroy,
+	gaim_gtkxfer_request_file,
+	gaim_gtkxfer_ask_cancel,
+	gaim_gtkxfer_add_xfer,
+	gaim_gtkxfer_update_progress,
+	gaim_gtkxfer_cancel
+};
+
+void
+gaim_gtkxfer_dialog_show(void)
+{
+	if (xfer_dialog == NULL)
+		xfer_dialog = build_xfer_dialog();
+
+	gtk_widget_show(xfer_dialog->window);
+}
+
+void
+gaim_gtkxfer_dialog_hide(void)
+{
+	gtk_widget_hide(xfer_dialog->window);
+}
+
+struct gaim_xfer_ui_ops *
+gaim_get_gtk_xfer_ui_ops(void)
+{
+	return &ops;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkft.h	Tue Feb 04 06:57:35 2003 +0000
@@ -0,0 +1,50 @@
+/**
+ * @file gtkft.h The GTK+ file transfer UI
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * 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.
+ *
+ * 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
+ *
+ */
+#ifndef _GAIM_GTK_FT_H_
+#define _GAIM_GTK_FT_H_
+
+/**************************************************************************/
+/** @name GTK+ File Transfer API                                          */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Displays the file transfer dialog.
+ */
+void gaim_gtkxfer_dialog_show(void);
+
+/**
+ * Hides the file transfer dialog.
+ */
+void gaim_gtkxfer_dialog_hide(void);
+
+/**
+ * Returns the UI operations structure for the GTK+ file transfer UI.
+ *
+ * @return The GTK+ file transfer UI operations structure.
+ */
+struct gaim_xfer_ui_ops *gaim_get_gtk_xfer_ui_ops(void);
+
+/*@}*/
+
+#endif /* _GAIM_GTK_FT_H_ */
--- a/src/main.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/main.c	Tue Feb 04 06:57:35 2003 +0000
@@ -520,7 +520,9 @@
 		}
 	}
 
+	/* Set the UI operation structures. */
 	gaim_set_win_ui_ops(gaim_get_gtk_window_ui_ops());
+	gaim_set_xfer_ui_ops(gaim_get_gtk_xfer_ui_ops());
 
 	setup_stock();
 
--- a/src/protocols/irc/irc.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/protocols/irc/irc.c	Tue Feb 04 06:57:35 2003 +0000
@@ -80,19 +80,12 @@
 	char nick[80];		
 };
 
-struct irc_file_transfer {
-	enum { IFT_SENDFILE_IN, IFT_SENDFILE_OUT } type;
-	struct file_transfer *xfer;
-	char *sn;
-	char *name;
-	int len;
-	int watcher;
-	int awatcher;
-	char ip[12];
+struct irc_xfer_data
+{
+	char *ip;
 	int port;
-	int fd;
-	int cur;
-	struct gaim_connection *gc;
+
+	struct irc_data *idata;
 };
 
 struct irc_data {
@@ -513,16 +506,6 @@
 	}
 }
 
-static void 
-irc_file_transfer_do(struct gaim_connection *gc, struct irc_file_transfer *ift) {
-	/* Ok, we better be receiving some crap here boyeee */
-	if (transfer_in_do(ift->xfer, ift->fd, ift->name, ift->len)) {
-		gaim_input_remove(ift->watcher);
-		ift->watcher = 0;
-	}
-}
-
-
 void 
 irc_read_dcc_ack (gpointer data, gint source, GaimInputCondition condition) {
 	/* Read ACK Here */
@@ -531,6 +514,7 @@
 
 void 
 dcc_send_callback (gpointer data, gint source, GaimInputCondition condition) {
+#if 0
 	struct irc_file_transfer *ift = data;
 	struct sockaddr_in addr;
 	int len = sizeof(addr);
@@ -552,14 +536,7 @@
 		gaim_input_remove(ift->watcher);
 		ift->watcher = 0;
 	}
-}
-
-void 
-dcc_recv_callback (gpointer data, gint source, GaimInputCondition condition) {
-	struct irc_file_transfer *ift = data;
-	
-	ift->fd = source;
-	irc_file_transfer_do(ift->gc, ift);
+#endif
 }
 
 void 
@@ -1247,6 +1224,48 @@
 	dcc_chat_cancel(dchat);
 }
 
+static void
+irc_xfer_init(struct gaim_xfer *xfer)
+{
+	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
+
+	gaim_xfer_start(xfer, -1, data->ip, data->port);
+}
+
+static void
+irc_xfer_end(struct gaim_xfer *xfer)
+{
+	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
+
+	data->idata->file_transfers = g_slist_remove(data->idata->file_transfers,
+												 xfer);
+
+	g_free(data);
+	xfer->data = NULL;
+}
+
+static void
+irc_xfer_cancel(struct gaim_xfer *xfer)
+{
+	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
+
+	data->idata->file_transfers = g_slist_remove(data->idata->file_transfers,
+												 xfer);
+
+	g_free(data);
+	xfer->data = NULL;
+}
+
+static void
+irc_xfer_ack(struct gaim_xfer *xfer)
+{
+	guint32 pos;
+
+	pos = htonl(gaim_xfer_get_bytes_sent(xfer));
+
+	gaim_xfer_write(xfer, (char *)&pos, 4);
+}
+
 static void 
 handle_ctcp(struct gaim_connection *gc, char *to, char *nick,
 			char *msg, char *word[], char *word_eol[])
@@ -1302,22 +1321,76 @@
 
 
 	if (!g_strncasecmp(msg, "DCC SEND", 8)) {
-		struct irc_file_transfer *ift = g_new0(struct irc_file_transfer, 1);
-		char **send_args = g_strsplit(msg, " ", 6);
+		struct gaim_xfer *xfer;
+		char **send_args;
+		char *ip, *filename;
+		struct irc_xfer_data *xfer_data;
+		size_t size;
+		int port;
+
+		send_args = g_strsplit(msg, " ", 6);
 		send_args[5][strlen(send_args[5])-1] = 0;
 
-		ift->type = IFT_SENDFILE_IN;
-		ift->sn = g_strdup(nick);
-		ift->gc = gc;
-		g_snprintf(ift->ip, sizeof(ift->ip), send_args[3]);	
-		ift->port = atoi(send_args[4]);
-		ift->len = atoi(send_args[5]);
-		ift->name = g_strdup(send_args[2]);
-		ift->cur = 0;
+		/* Give these better names. */
+		ip       = send_args[3];
+		filename = send_args[2];
+		size     = atoi(send_args[5]);
+		port     = atoi(send_args[4]);
+
+		/* Setup the IRC-specific transfer data. */
+		xfer_data = g_malloc0(sizeof(struct irc_xfer_data));
+		xfer_data->ip    = ip;
+		xfer_data->port  = port;
+		xfer_data->idata = id;
+
+		/* Build the file transfer handle. */
+		xfer = gaim_xfer_new(gc->account, GAIM_XFER_RECEIVE, nick);
+		xfer->data = xfer_data;
+
+		/* Set the info about the incoming file. */
+		gaim_xfer_set_filename(xfer, filename);
+		gaim_xfer_set_size(xfer, size);
+
+		g_free(filename);
+
+		/* Setup our I/O op functions. */
+		gaim_xfer_set_init_fnc(xfer,   irc_xfer_init);
+		gaim_xfer_set_end_fnc(xfer,    irc_xfer_end);
+		gaim_xfer_set_cancel_fnc(xfer, irc_xfer_cancel);
+		gaim_xfer_set_ack_fnc(xfer,    irc_xfer_ack);
 
-		id->file_transfers = g_slist_append(id->file_transfers, ift);
+		/* Keep track of this transfer for later. */
+		id->file_transfers = g_slist_append(id->file_transfers, xfer);
+
+		/* Now perform the request! */
+		gaim_xfer_request(xfer);
+
+#if 0
+		if (xfer != NULL) {
+			struct irc_file_transfer *ift;
+			gaim_xfer_set_read_fnc(xfer,   irc_xfer_read);
+			gaim_xfer_set_cancel_fnc(xfer, irc_xfer_cancel);
+
+			gaim_xfer_set_ack_fnc(xfer, irc_xfer_ack);
+
+			ift = g_new0(struct irc_file_transfer, 1);
 
-		ift->xfer = transfer_in_add(gc, nick, ift->name, ift->len, 1, NULL);
+			strncpy(ift->ip, send_args[3], sizeof(ift->ip));
+			ift->type = IFT_SENDFILE_IN;
+			ift->sn   = g_strdup(nick);
+			ift->gc   = gc;
+			ift->port = atoi(send_args[4]);
+			ift->len  = atoi(send_args[5]);
+			ift->name = g_strdup(send_args[2]);
+			ift->cur  = 0;
+			ift->xfer = xfer;
+
+			xfer->data = ift;
+
+
+			gaim_xfer_start(xfer, -1, send_args[3], atoi(send_args[4]));
+		}
+#endif
 	}
 
 	/*write_to_conv(c, out, WFLAG_SYSTEM, NULL, time(NULL), -1);*/
@@ -1779,13 +1852,12 @@
 
 	/* Kill any existing transfers */
 	while (idata->file_transfers) {
-		struct irc_file_transfer *ift = (struct irc_file_transfer *)idata->file_transfers->data;
+		struct gaim_xfer *xfer;
 
-		g_free(ift->sn);
-		g_free(ift->name);
-		gaim_input_remove(ift->watcher);
+		xfer = (struct gaim_xfer *)idata->file_transfers->data;
 
-		close(ift->fd);
+		gaim_xfer_end(xfer);
+		gaim_xfer_destroy(xfer);
 
 		idata->file_transfers = idata->file_transfers->next;
 	}
@@ -2423,10 +2495,10 @@
 
 	ift->xfer = transfer_out_add(gc, ift->sn);
 }
-#endif
+
 static struct 
-irc_file_transfer *find_ift_by_xfer(struct gaim_connection *gc, 
-						  struct file_transfer *xfer) {
+irc_file_transfer *find_ift_by_xfer(struct gaim_connection *gc,
+									struct file_transfer *xfer) {
 		
 	GSList *g = ((struct irc_data *)gc->proto_data)->file_transfers;
 	struct irc_file_transfer *f = NULL;
@@ -2535,6 +2607,7 @@
 	ift->xfer = xfer;
 	proxy_connect(ift->ip, ift->port, dcc_recv_callback, ift);
 }
+#endif
 
 static void 
 irc_ctcp_clientinfo(struct gaim_connection *gc, char *who)
@@ -2746,11 +2819,13 @@
 	ret->buddy_menu = irc_buddy_menu;
 	ret->chat_invite = irc_chat_invite;
 	ret->convo_closed = irc_convo_closed;
+#if 0
 	ret->file_transfer_out = irc_file_transfer_out; 
 	ret->file_transfer_in = irc_file_transfer_in;
 	ret->file_transfer_data_chunk = irc_file_transfer_data_chunk;
 	ret->file_transfer_done = irc_file_transfer_done;
 	ret->file_transfer_cancel =irc_file_transfer_cancel;
+#endif
 
 	puo = g_new0(struct proto_user_opt, 1);
 	puo->label = g_strdup(_("Server:"));
--- a/src/prpl.h	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/prpl.h	Tue Feb 04 06:57:35 2003 +0000
@@ -85,8 +85,6 @@
 
 typedef void (*proto_init)(struct prpl *);
 
-struct file_transfer;
-
 struct prpl {
 	int protocol;
 	int options;
@@ -179,16 +177,6 @@
 	void (* convo_closed)   (struct gaim_connection *, char *who);
 
 	char *(* normalize)(const char *);
-
-	/* transfer files */
-	void (* file_transfer_cancel)	 (struct gaim_connection *, struct file_transfer *);
-	void (* file_transfer_in)	 (struct gaim_connection *, struct file_transfer *, int);
-	void (* file_transfer_out)	 (struct gaim_connection *, struct file_transfer *, const char *, int, int);
-	void (* file_transfer_nextfile)	 (struct gaim_connection *, struct file_transfer *);
-	void (* file_transfer_data_chunk)(struct gaim_connection *, struct file_transfer *, const char *, int);
-	void (* file_transfer_done)	 (struct gaim_connection *, struct file_transfer *);
-	size_t (* file_transfer_read) (struct gaim_connection *, struct file_transfer *, int fd, char **buf);
-	size_t (* file_transfer_write) (struct gaim_connection *, struct file_transfer *, int fd, const char *buf, size_t size);
 };
 
 extern GSList *protocols;
@@ -218,20 +206,6 @@
 extern void set_icon_data(struct gaim_connection *, char *, void *, int);
 extern void *get_icon_data(struct gaim_connection *, char *, int *);
 
-/* file transfer stuff */
-extern struct file_transfer *transfer_in_add(struct gaim_connection *gc,
-		const char *who, const char *filename, int totsize,
-		int totfiles, const char *msg);
-extern struct file_transfer *transfer_out_add(struct gaim_connection *gc,
-		const char *who);
-extern int transfer_abort(struct file_transfer *xfer, const char *why);
-extern int transfer_out_do(struct file_transfer *xfer, int fd,
-		int offset);
-extern int transfer_in_do(struct file_transfer *xfer, int fd,
-		const char *filename, int size);
-int transfer_get_file_info(struct file_transfer *xfer, int *size,
-		char **name);
-
 /* stuff to load/unload PRPLs as necessary */
 extern gboolean ref_protocol(struct prpl *p);
 extern void unref_protocol(struct prpl *p);
--- a/src/stock.c	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/stock.c	Tue Feb 04 06:57:35 2003 +0000
@@ -39,7 +39,10 @@
 {
 	{ GAIM_STOCK_BGCOLOR,       "buttons", "change-bgcolor-small.png" },
 	{ GAIM_STOCK_BLOCK,         NULL,      GTK_STOCK_STOP             },
+	{ GAIM_STOCK_DOWNLOAD,      NULL,      GTK_STOCK_GO_DOWN          },
 	{ GAIM_STOCK_FGCOLOR,       "buttons", "change-fgcolor-small.png" },
+	{ GAIM_STOCK_FILE_CANCELED, NULL,      GTK_STOCK_CANCEL           },
+	{ GAIM_STOCK_FILE_DONE,     NULL,      GTK_STOCK_APPLY            },
 	{ GAIM_STOCK_IGNORE,        NULL,      GTK_STOCK_DIALOG_ERROR     },
 	{ GAIM_STOCK_IMAGE,         "menus",   "insert-image-small.png"   },
 	{ GAIM_STOCK_INFO,          NULL,      GTK_STOCK_FIND             },
@@ -50,6 +53,7 @@
 	{ GAIM_STOCK_TEXT_BIGGER,   "buttons", "text_bigger.png"          },
 	{ GAIM_STOCK_TEXT_NORMAL,   "buttons", "text_normal.png"          },
 	{ GAIM_STOCK_TEXT_SMALLER,  "buttons", "text_smaller.png"         },
+	{ GAIM_STOCK_UPLOAD,        NULL,      GTK_STOCK_GO_UP            },
 	{ GAIM_STOCK_WARN,          NULL,      GTK_STOCK_DIALOG_WARNING   }
 };
 
--- a/src/stock.h	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/stock.h	Tue Feb 04 06:57:35 2003 +0000
@@ -29,7 +29,10 @@
 /*@{*/
 #define GAIM_STOCK_BGCOLOR        "gaim-bgcolor"
 #define GAIM_STOCK_BLOCK          "gaim-block"
+#define GAIM_STOCK_DOWNLOAD       "gaim-download"
 #define GAIM_STOCK_FGCOLOR        "gaim-fgcolor"
+#define GAIM_STOCK_FILE_CANCELED  "gaim-file-canceled"
+#define GAIM_STOCK_FILE_DONE      "gaim-file-done"
 #define GAIM_STOCK_IGNORE         "gaim-ignore"
 #define GAIM_STOCK_IMAGE          "gaim-image"
 #define GAIM_STOCK_INFO           "gaim-info"
@@ -40,6 +43,7 @@
 #define GAIM_STOCK_TEXT_BIGGER    "gaim-text-bigger"
 #define GAIM_STOCK_TEXT_NORMAL    "gaim-text-normal"
 #define GAIM_STOCK_TEXT_SMALLER   "gaim-text-smaller"
+#define GAIM_STOCK_UPLOAD         "gaim-upload"
 #define GAIM_STOCK_WARN           "gaim-warn"
 /*@}*/
 
--- a/src/ui.h	Tue Feb 04 05:25:46 2003 +0000
+++ b/src/ui.h	Tue Feb 04 06:57:35 2003 +0000
@@ -30,6 +30,7 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 #include "gtkconv.h"
+#include "gtkft.h"
 #include "gtkutils.h"
 #include "stock.h"