changeset 15293:0e8f0468a809

[gaim-migrate @ 18083] Patch from Graham Booker to change the way we do file checksums for AIM and ICQ. Instead of checksumming the entire file in a single go (which can take a lot of time if you're sending a 2GB file to someone), checksum the file 256KB at a time and return to the glib mainloop between chunks. The result is that the UI is responsive while the checksum is being calculated. It's really quite pleasant. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Sun, 07 Jan 2007 20:05:46 +0000
parents 40746bedd9a8
children 4bcc40bd4e20
files libgaim/protocols/oscar/oft.c libgaim/protocols/oscar/peer.c libgaim/protocols/oscar/peer.h
diffstat 3 files changed, 171 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/libgaim/protocols/oscar/oft.c	Sun Jan 07 07:30:01 2007 +0000
+++ b/libgaim/protocols/oscar/oft.c	Sun Jan 07 20:05:46 2007 +0000
@@ -58,6 +58,31 @@
 
 #include "util.h"
 
+#define CHECKSUM_BUFFER_SIZE 256 * 1024
+
+struct _ChecksumData
+{
+	PeerConnection *conn;
+	GaimXfer *xfer;
+	GSourceFunc callback;
+	size_t size;
+	guint32 checksum;
+	size_t total;
+	FILE *file;
+	guint8 buffer[CHECKSUM_BUFFER_SIZE];
+	guint timer;
+};
+
+void
+peer_oft_checksum_destroy(ChecksumData *checksum_data)
+{
+	checksum_data->conn->checksum_data = NULL;
+	fclose(checksum_data->file);
+	if (checksum_data->timer > 0)
+		gaim_timeout_remove(checksum_data->timer);
+	g_free(checksum_data);
+}
+
 /**
  * Calculate oft checksum of buffer
  *
@@ -78,16 +103,29 @@
  * @param buffer Buffer of data to checksum.  Man I'd like to buff her...
  * @param bufsize Size of buffer.
  * @param prevchecksum Previous checksum.
+ * @param odd Whether an odd number of bytes have been processed before this call
  */
 static guint32
-peer_oft_checksum_chunk(const guint8 *buffer, int bufferlen, guint32 prevchecksum)
+peer_oft_checksum_chunk(const guint8 *buffer, int bufferlen, guint32 prevchecksum, int odd)
 {
 	guint32 checksum, oldchecksum;
-	int i;
+	int i = 0;
 	unsigned short val;
 
 	checksum = (prevchecksum >> 16) & 0xffff;
-	for (i = 0; i < bufferlen; i++)
+	if (odd)
+	{
+		/*
+		 * This is one hell of a hack, but it should always work.
+		 * Essentially, I am reindexing the array so that index 1
+		 * is the first element.  Since the odd and even bytes are
+		 * detected by the index number.
+		 */
+		i = 1;
+		bufferlen++;
+		buffer--;
+	}
+	for (; i < bufferlen; i++)
 	{
 		oldchecksum = checksum;
 		if (i & 1)
@@ -107,24 +145,83 @@
 	return checksum << 16;
 }
 
-static guint32
-peer_oft_checksum_file(char *filename)
+static gboolean
+peer_oft_checksum_file_piece(gpointer data)
 {
-	FILE *fd;
-	guint32 checksum = 0xffff0000;
+	ChecksumData *checksum_data;
+	gboolean repeat;
 
-	if ((fd = fopen(filename, "rb")))
+	checksum_data = data;
+	repeat = FALSE;
+
+	if (checksum_data->total < checksum_data->size)
 	{
-		int bytes;
-		guint8 *buffer = g_malloc(65536);
+		size_t bytes = MIN(CHECKSUM_BUFFER_SIZE,
+				checksum_data->size - checksum_data->total);
 
-		while ((bytes = fread(buffer, 1, 65536, fd)) != 0)
-			checksum = peer_oft_checksum_chunk(buffer, bytes, checksum);
-		g_free(buffer);
-		fclose(fd);
+		bytes = fread(checksum_data->buffer, 1, bytes, checksum_data->file);
+		if (bytes != 0)
+		{
+			checksum_data->checksum = peer_oft_checksum_chunk(checksum_data->buffer, bytes, checksum_data->checksum, checksum_data->total & 1);
+			checksum_data->total += bytes;
+			repeat = TRUE;
+		}
+	}
+
+	if (!repeat)
+	{
+		gaim_debug_info("oscar", "Checksum of %s calculated\n",
+				gaim_xfer_get_local_filename(checksum_data->xfer));
+		if (checksum_data->callback != NULL)
+			checksum_data->callback(checksum_data);
+		peer_oft_checksum_destroy(checksum_data);
 	}
 
-	return checksum;
+	return repeat;
+}
+
+/**
+ * Calculate oft checksum of a file in a series of calls to
+ * peer_oft_checksum_file_piece().  We do it this way because
+ * calculating the checksum on large files can take a long time,
+ * and we want to return control to the UI so that the application
+ * doesn't appear completely frozen.
+ *
+ * @param conn The connection used for this file transfer.
+ * @param xfer The file transfer needing this checksum.
+ * @param callback The function to call upon calculation of the checksum.
+ * @param size The maximum size to check.
+ */
+
+static void
+peer_oft_checksum_file(PeerConnection *conn, GaimXfer *xfer, GSourceFunc callback, size_t size)
+{
+	ChecksumData *checksum_data;
+
+	gaim_debug_info("oscar", "Calculating checksum of %s\n",
+			gaim_xfer_get_local_filename(xfer));
+
+	checksum_data = g_malloc0(sizeof(ChecksumData));
+	checksum_data->conn = conn;
+	checksum_data->xfer = xfer;
+	checksum_data->callback = callback;
+	checksum_data->size = size;
+	checksum_data->checksum = 0xffff0000;
+	checksum_data->file = fopen(gaim_xfer_get_local_filename(xfer), "rb");
+
+	if (checksum_data->file == NULL)
+	{
+		gaim_debug_error("oscar", "Unable to open %s for checksumming: %s\n",
+				gaim_xfer_get_local_filename(xfer), strerror(errno));
+		callback(checksum_data);
+		g_free(checksum_data);
+	}
+	else
+	{
+		checksum_data->timer = gaim_timeout_add(10,
+				peer_oft_checksum_file_piece, checksum_data);
+		conn->checksum_data = checksum_data;
+	}
 }
 
 static void
@@ -341,6 +438,32 @@
 			start_transfer_when_done_sending_data, conn);
 }
 
+static gboolean
+peer_oft_recv_frame_resume_checksum_calculated_cb(gpointer data)
+{
+	ChecksumData *checksum_data;
+	PeerConnection *conn;
+
+	checksum_data = data;
+	conn = checksum_data->conn;
+
+	/* Check the checksums here.  If not match, don't allow resume */
+	if (checksum_data->checksum != conn->xferdata.recvcsum || checksum_data->total != conn->xferdata.nrecvd)
+	{
+		/* Reset internal structure */
+		conn->xferdata.recvcsum = 0xffff0000;
+		conn->xferdata.rfrcsum = 0xffff0000;
+		conn->xferdata.nrecvd = 0;
+	}
+	else
+		/* Accept the change */
+		gaim_xfer_set_bytes_sent(checksum_data->xfer, conn->xferdata.nrecvd);
+
+	peer_oft_send_resume_accept(conn);
+
+	return FALSE;
+}
+
 /**
  * We are sending a file to someone else.  They have just acknowledged our
  * prompt and are asking to resume, so we accept their resume and await
@@ -357,18 +480,14 @@
 		return;
 	}
 
-	/*
-	 * TODO: Check the checksums here.  If they don't match then don't
-	 *       copy the data like below.
-	 */
-
 	/* Copy resume data into internal structure */
 	conn->xferdata.recvcsum = frame->recvcsum;
 	conn->xferdata.rfrcsum = frame->rfrcsum;
 	conn->xferdata.nrecvd = frame->nrecvd;
 
-	gaim_xfer_set_bytes_sent(conn->xfer, frame->nrecvd);
-	peer_oft_send_resume_accept(conn);
+	peer_oft_checksum_file(conn, conn->xfer,
+			peer_oft_recv_frame_resume_checksum_calculated_cb,
+			frame->nrecvd);
 }
 
 /*
@@ -491,7 +610,7 @@
 	/* Update our rolling checksum.  Like Walmart, yo. */
 	conn = xfer->data;
 	conn->xferdata.recvcsum = peer_oft_checksum_chunk(buffer,
-			size, conn->xferdata.recvcsum);
+			size, conn->xferdata.recvcsum, gaim_xfer_get_bytes_sent(xfer) & 1);
 }
 
 /*******************************************************************/
@@ -502,6 +621,23 @@
 /* Begin GaimXfer callbacks for use when sending a file            */
 /*******************************************************************/
 
+static gboolean
+peer_oft_checksum_calculated_cb(gpointer data)
+{
+	ChecksumData *checksum_data;
+	PeerConnection *conn;
+
+	checksum_data = data;
+	conn = checksum_data->conn;
+
+	conn->xferdata.checksum = checksum_data->checksum;
+
+	/* Start the connection process */
+	peer_connection_trynext(checksum_data->conn);
+
+	return FALSE;
+}
+
 void
 peer_oft_sendcb_init(GaimXfer *xfer)
 {
@@ -548,13 +684,8 @@
 	conn->xferdata.name = (guchar *)g_strdup(xfer->filename);
 	conn->xferdata.name_length = strlen(xfer->filename);
 
-	/* Calculating the checksum can take a very long time for large files */
-	gaim_debug_info("oscar","calculating file checksum\n");
-	conn->xferdata.checksum = peer_oft_checksum_file(xfer->local_filename);
-	gaim_debug_info("oscar","checksum calculated\n");
-
-	/* Start the connection process */
-	peer_connection_trynext(conn);
+	peer_oft_checksum_file(conn, xfer,
+			peer_oft_checksum_calculated_cb, G_MAXUINT32);
 }
 
 /*
--- a/libgaim/protocols/oscar/peer.c	Sun Jan 07 07:30:01 2007 +0000
+++ b/libgaim/protocols/oscar/peer.c	Sun Jan 07 20:05:46 2007 +0000
@@ -206,6 +206,9 @@
 
 	peer_connection_close(conn);
 
+	if (conn->checksum_data != NULL)
+		peer_oft_checksum_destroy(conn->checksum_data);
+
 	if (conn->xfer != NULL)
 	{
 		GaimXferStatusType status;
--- a/libgaim/protocols/oscar/peer.h	Sun Jan 07 07:30:01 2007 +0000
+++ b/libgaim/protocols/oscar/peer.h	Sun Jan 07 20:05:46 2007 +0000
@@ -29,6 +29,7 @@
 #include "network.h"
 #include "proxy.h"
 
+typedef struct _ChecksumData          ChecksumData;
 typedef struct _OdcFrame              OdcFrame;
 typedef struct _OftFrame              OftFrame;
 typedef struct _ProxyFrame            ProxyFrame;
@@ -205,6 +206,11 @@
 	guint16 port;
 	gboolean use_proxy;
 
+	/**
+	 * Checksumming
+	 */
+	ChecksumData *checksum_data;
+
 	/* TODOFT */
 	GaimXfer *xfer;
 	OftFrame xferdata;
@@ -253,6 +259,7 @@
 void peer_oft_close(PeerConnection *conn);
 void peer_oft_recv_frame(PeerConnection *conn, ByteStream *bs);
 void peer_oft_send_prompt(PeerConnection *conn);
+void peer_oft_checksum_destroy(ChecksumData *checksum_data);
 
 /* Xfer callbacks for receiving a file */
 void peer_oft_recvcb_init(GaimXfer *xfer);