changeset 27872:4a4e9d309fc0

ft: Allow the UI to overloadthe use of fread/fwrite. Closes #9844. Patch from Jan "HanzZ" Kaluza with a few changes by darkrain42. committer: Paul Aurich <paul@darkrain42.org>
author hanzz@soc.pidgin.im
date Mon, 10 Aug 2009 15:57:35 +0000
parents 2075b5cb608e
children 5a38fab8c199
files ChangeLog.API libpurple/ft.c libpurple/ft.h
diffstat 3 files changed, 150 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Mon Aug 10 04:40:54 2009 +0000
+++ b/ChangeLog.API	Mon Aug 10 15:57:35 2009 +0000
@@ -18,6 +18,9 @@
 		* Three Blist UI ops used to overload libpurple's built-in saving
 		  of the buddy list to blist.xml. If a UI implements these, it probably
 		  wants to add the buddies itself and not call purple_blist_load.
+		* Three File Transfer UI ops used to overload libpurple's use of fread
+		  and fwrite for saving a file locally. These allow a UI to stream a
+		  file through a socket without buffering the file on the local disk.
 		* Jabber plugin signals (see jabber-signals.dox)
 		* purple_account_remove_setting
 		* purple_buddy_destroy
@@ -65,6 +68,7 @@
 		* purple_strequal
 		* purple_utf8_strip_unprintables
 		* purple_util_fetch_url_request_len_with_account
+		* purple_xfer_ui_ready
 		* xmlnode_from_file
 		* xmlnode_get_parent
 		* xmlnode_set_attrib_full
--- a/libpurple/ft.c	Mon Aug 10 04:40:54 2009 +0000
+++ b/libpurple/ft.c	Mon Aug 10 15:57:35 2009 +0000
@@ -482,13 +482,16 @@
 	if (type == PURPLE_XFER_SEND) {
 		/* Sending a file */
 		/* Check the filename. */
+		PurpleXferUiOps *ui_ops;
+		ui_ops = purple_xfer_get_ui_ops(xfer);
+
 #ifdef _WIN32
 		if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
 #else
 		if (g_strrstr(filename, "../"))
 #endif
 		{
-			char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
+			utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
 
 			msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
 			purple_xfer_error(type, account, xfer->who, msg);
@@ -499,15 +502,20 @@
 			return;
 		}
 
-		if (g_stat(filename, &st) == -1) {
-			purple_xfer_show_file_error(xfer, filename);
-			purple_xfer_unref(xfer);
-			return;
+		if (ui_ops == NULL || (ui_ops->read == NULL && ui_ops->write == NULL)) {
+			if (g_stat(filename, &st) == -1) {
+				purple_xfer_show_file_error(xfer, filename);
+				purple_xfer_unref(xfer);
+				return;
+			}
+
+			purple_xfer_set_local_filename(xfer, filename);
+			purple_xfer_set_size(xfer, st.st_size);
+		} else {
+			utf8 = g_strdup(filename);
+			purple_xfer_set_local_filename(xfer, filename);
 		}
 
-		purple_xfer_set_local_filename(xfer, filename);
-		purple_xfer_set_size(xfer, st.st_size);
-
 		base = g_path_get_basename(filename);
 		utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL);
 		g_free(base);
@@ -516,7 +524,6 @@
 		msg = g_strdup_printf(_("Offering to send %s to %s"),
 				utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
 		g_free(utf8);
-
 		purple_xfer_conversation_write(xfer, msg, FALSE);
 		g_free(msg);
 	}
@@ -946,10 +953,17 @@
 	guchar *buffer = NULL;
 	gssize r = 0;
 
+	ui_ops = purple_xfer_get_ui_ops(xfer);
+	
 	if (condition & PURPLE_INPUT_READ) {
 		r = purple_xfer_read(xfer, &buffer);
 		if (r > 0) {
-			const size_t wc = fwrite(buffer, 1, r, xfer->dest_fp);
+			size_t wc;
+			if (ui_ops && ui_ops->write)
+				wc = ui_ops->write(xfer, buffer, r);
+			else
+				wc = fwrite(buffer, 1, r, xfer->dest_fp);
+
 			if (wc != r) {
 				purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
 				purple_xfer_cancel_local(xfer);
@@ -975,26 +989,53 @@
 			return;
 		}
 
-		buffer = g_malloc0(s);
+		if (ui_ops && ui_ops->read) {
+			gssize tmp = ui_ops->read(xfer, &buffer, s);
+			if (tmp == 0) {
+				/*
+				 * UI isn't ready to send data. It will call
+				 * purple_xfer_ui_ready when ready, which sets back up this
+				 * watcher.
+				 */
+				if (xfer->watcher != 0) {
+					purple_timeout_remove(xfer->watcher);
+					xfer->watcher = 0;
+				}
 
-		result = fread(buffer, 1, s, xfer->dest_fp);
-		if (result != s) {
-			purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
-			purple_xfer_cancel_remote(xfer);
-			g_free(buffer);
-			return;
+				return;
+			} else if (tmp < 0) {
+				purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
+				purple_xfer_cancel_local(xfer);
+				return;
+			}
+
+			result = tmp;
+		} else {
+			buffer = g_malloc0(s);
+			result = fread(buffer, 1, s, xfer->dest_fp);
+			if (result != s) {
+				purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
+				purple_xfer_cancel_local(xfer);
+				g_free(buffer);
+				return;
+			}
 		}
 
 		/* Write as much as we're allowed to. */
-		r = purple_xfer_write(xfer, buffer, s);
+		r = purple_xfer_write(xfer, buffer, result);
 
 		if (r == -1) {
 			purple_xfer_cancel_remote(xfer);
 			g_free(buffer);
 			return;
-		} else if (r < s) {
-			/* We have to seek back in the file now. */
-			fseek(xfer->dest_fp, r - s, SEEK_CUR);
+		} else if (r < result) {
+			if (ui_ops == NULL || (ui_ops->read == NULL && ui_ops->write == NULL)) {
+				/* We have to seek back in the file now. */
+				fseek(xfer->dest_fp, r - s, SEEK_CUR);
+			}
+			else {
+				ui_ops->data_not_sent(xfer, buffer + r, result - r);
+			}
 		} else {
 			/*
 			 * We managed to write the entire buffer.  This means our
@@ -1016,8 +1057,6 @@
 
 		g_free(buffer);
 
-		ui_ops = purple_xfer_get_ui_ops(xfer);
-
 		if (ui_ops != NULL && ui_ops->update_progress != NULL)
 			ui_ops->update_progress(xfer,
 				purple_xfer_get_progress(xfer));
@@ -1031,18 +1070,21 @@
 begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond)
 {
 	PurpleXferType type = purple_xfer_get_type(xfer);
+	PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer);
 
-	xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
-						  type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
+	if (ui_ops == NULL || (ui_ops->read == NULL && ui_ops->write == NULL)) {
+		xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
+		                        type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
 
-	if (xfer->dest_fp == NULL) {
-		purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
-		purple_xfer_cancel_local(xfer);
-		return;
+		if (xfer->dest_fp == NULL) {
+			purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
+			purple_xfer_cancel_local(xfer);
+			return;
+		}
+
+		fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
 	}
 
-	fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
-
 	if (xfer->fd)
 		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
 
@@ -1068,6 +1110,26 @@
 }
 
 void
+purple_xfer_ui_ready(PurpleXfer *xfer)
+{
+	PurpleInputCondition cond;
+	PurpleXferType type;
+
+	g_return_if_fail(xfer != NULL);
+
+	type = purple_xfer_get_type(xfer);
+	if (type == PURPLE_XFER_SEND)
+		cond = PURPLE_INPUT_WRITE;
+	else /* if (type == PURPLE_XFER_RECEIVE) */
+		cond = PURPLE_INPUT_READ;
+
+	if (xfer->watcher == 0)
+		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
+
+	transfer_cb(xfer, 0, cond);
+}
+
+void
 purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
 				unsigned int port)
 {
--- a/libpurple/ft.h	Mon Aug 10 04:40:54 2009 +0000
+++ b/libpurple/ft.h	Mon Aug 10 15:57:35 2009 +0000
@@ -77,10 +77,50 @@
 	void (*cancel_local)(PurpleXfer *xfer);
 	void (*cancel_remote)(PurpleXfer *xfer);
 
+	/**
+	 * UI op to write data received from the prpl. The UI must deal with the
+	 * entire buffer and return size, or it is treated as an error.
+	 *
+	 * @param xfer    The file transfer structure
+	 * @param buffer  The buffer to write
+	 * @param size    The size of the buffer
+	 *
+	 * @return size if the write was successful, or a value between 0 and
+	 *         size on error.
+	 * @since 2.6.0
+	 */
+	gssize (*write)(PurpleXfer *xfer, const guchar *buffer, gssize size);
+
+	/**
+	 * UI op to read data to send to the prpl for a file transfer.
+	 *
+	 * @param xfer    The file transfer structure
+	 * @param buffer  A pointer to a buffer. The UI must allocate this buffer.
+	 *                libpurple will free the data.
+	 * @param size    The maximum amount of data to put in the buffer.
+	 *
+	 * @returns The amount of data in the buffer, 0 if nothing is available,
+	 *          and a negative value if an error occurred and the transfer
+	 *          should be cancelled (libpurple will cancel).
+	 * @since 2.6.0
+	 */
+	gssize (*read)(PurpleXfer *xfer, guchar **buffer, gssize size);
+
+	/**
+	 * Op to notify the UI that not all the data read in was written. The UI
+	 * should re-enqueue this data and return it the next time read is called.
+	 *
+	 * This MUST be implemented if read and write are implemented.
+	 *
+	 * @param xfer    The file transfer structure
+	 * @param buffer  A pointer to the beginning of the unwritten data.
+	 * @param size    The amount of unwritten data.
+	 *
+	 * @since 2.6.0
+	 */
+	void (*data_not_sent)(PurpleXfer *xfer, const guchar *buffer, gsize size);
+
 	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
-	void (*_purple_reserved3)(void);
-	void (*_purple_reserved4)(void);
 } PurpleXferUiOps;
 
 /**
@@ -132,7 +172,6 @@
 		gssize (*read)(guchar **buffer, PurpleXfer *xfer);
 		gssize (*write)(const guchar *buffer, size_t size, PurpleXfer *xfer);
 		void (*ack)(PurpleXfer *xfer, const guchar *buffer, size_t size);
-
 	} ops;
 
 	PurpleXferUiOps *ui_ops;            /**< UI-specific operations. */
@@ -617,6 +656,16 @@
  */
 void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error);
 
+/**
+ * Allows the UI to signal it's ready to send/receive data (depending on
+ * the direction of the file transfer.
+ *
+ * @param xfer The file transfer for which data is ready.
+ *
+ * @since 2.6.0
+ */
+void purple_xfer_ui_ready(PurpleXfer *xfer);
+
 /*@}*/
 
 /**************************************************************************/