changeset 28228:359800ae7d17

merge of '235259eaef44e8bacfe07e83eb5c66ddd4887ace' and '2417ca53bef6c584fa8f1e7d5d205793a7091496'
author maiku@pidgin.im
date Wed, 12 Aug 2009 10:04:54 +0000
parents dad4cb8f81df (diff) d865064a3104 (current diff)
children f5580e1ca4b1 de7f6fd04298
files
diffstat 15 files changed, 431 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Wed Aug 12 10:03:47 2009 +0000
+++ b/ChangeLog.API	Wed Aug 12 10:04:54 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,8 @@
 		* purple_strequal
 		* purple_utf8_strip_unprintables
 		* purple_util_fetch_url_request_len_with_account
+		* purple_xfer_prpl_ready
+		* purple_xfer_ui_ready
 		* xmlnode_from_file
 		* xmlnode_get_parent
 		* xmlnode_set_attrib_full
@@ -95,6 +100,10 @@
 		* status is set before emitting signals in purple_xfer_set_status.
 		* Creating multiple distinct chats with the same name (i.e. "MSN Chat")
 		  is deprecated and will be removed in libpurple 3.0.0.
+		* purple_xfer_start now accepts -1 as the fd parameter if the protocol
+		  plugin will administer the transfer itself. 0 is still accepted for
+		  backward compatibility since older versions of libpurple will not
+		  accept -1.
 
 		Deprecated:
 		* buddy-added and buddy-removed blist signals
--- a/doc/funniest_home_convos.txt	Wed Aug 12 10:03:47 2009 +0000
+++ b/doc/funniest_home_convos.txt	Wed Aug 12 10:04:54 2009 +0000
@@ -572,3 +572,42 @@
 15:46 <khc> well, there was a Grand Smiley Theme Database
 15:47 <SimGuy> the GSTD sounds like a bad acronym
 15:47 <khc> I realized after typing that
+
+--
+
+(01:51:38 AM) user entered the room.
+(01:52:46 AM) user: .addKeyActionListener(new KeyActionListener() onKeyPress() {if (event.geyKeyPresss().equals(Key.UP_ARROW) { inputbox.text = history.pop() }}}}}}});
+(01:52:51 AM) user: THERE, FOR **** SAKE
+(01:52:53 AM) user: its 2009
+(01:53:06 AM) user: oh wait. ctrl up works
+(01:53:07 AM) user: lol
+(01:53:11 AM) user: yey me
+(01:53:16 AM) user left the room.
+(01:55:31 AM) darkrain42: Wow.
+(01:58:15 AM) QuLogic: I think he failed to realize we'd have to re-write pidgin in java to do that
+(01:59:44 AM) khc: history.pop() is clearly wrong too
+
+--
+
+Some time later:
+(02:41:55 AM) user entered the room.
+(02:42:24 AM) user: didn't I read some idiot post, about 2 years ago, before pidgin was renamed / forked, over one dev refusing to make minimize on close?
+(02:43:12 AM) QuLogic: I see you've learned to at least ask a question before jumping to random conclusions
+(02:44:01 AM) user: QuLogic: :-))))))))))))))))))))))
+(02:44:12 AM) user: hey, I submitted a code patch!
+(02:44:36 AM) user: now, anyway, what happened? why did I get the buddy list (empty) stealing focus, and why did it exit on close?
+(02:44:40 AM) QuLogic: it's not really a patch if it's in the wrong language
+(02:44:42 AM) user: I've had this argument before, in 2006
+(02:44:55 AM) user: QuLogic: simple, rewrite the rest ;-)
+(02:44:58 AM) khc: pidgin never steals focus
+(02:45:10 AM) khc: if it exit on close, it's because you didn't turn on the systray icon
+(02:47:17 AM) user: khc - and that isn't default... why? anyway. I recall something on the matter, and I think this was the project (pre-fork?) or is this the unforked, renamed? I forget.
+(02:47:42 AM) user: Whoever it was arguing about it (and font sizes I believe) was an idiot... not one of you I suppose, just making idle chit chat.
+(02:47:43 AM) user: thanks
+(02:47:48 AM) darkrain42: It is on by default. Some distros change that.
+(02:48:00 AM) darkrain42: And I don't even know what you're arguing about at this point.
+(02:48:11 AM) user: ... /leave - That command doesn't work on this protocol... /leave #pidgin ...That comm..... :-(((
+(02:48:18 AM) user: darkrain42: now arguing, just remembering something
+(02:48:27 AM) user left the room.
+(02:49:04 AM) darkrain42: Wow. (again)
+
--- a/libpurple/ft.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/ft.c	Wed Aug 12 10:04:54 2009 +0000
@@ -40,8 +40,35 @@
 static PurpleXferUiOps *xfer_ui_ops = NULL;
 static GList *xfers;
 
+/*
+ * A hack to store more data since we can't extend the size of PurpleXfer
+ * easily.
+ */
+static GHashTable *xfers_data = NULL;
+
+typedef struct _PurpleXferPrivData {
+	/*
+	 * Used to moderate the file transfer when either the read/write ui_ops are
+	 * set or fd is not set. In those cases, the UI/prpl call the respective
+	 * function, which is somewhat akin to a fd watch being triggered.
+	 */
+	enum {
+		PURPLE_XFER_READY_NONE = 0x0,
+		PURPLE_XFER_READY_UI   = 0x1,
+		PURPLE_XFER_READY_PRPL = 0x2,
+	} ready;
+} PurpleXferPrivData;
+
 static int purple_xfer_choose_file(PurpleXfer *xfer);
 
+static void
+purple_xfer_priv_data_destroy(gpointer data)
+{
+	PurpleXferPrivData *priv = data;
+
+	g_free(priv);
+}
+
 GList *
 purple_xfers_get_all()
 {
@@ -53,6 +80,7 @@
 {
 	PurpleXfer *xfer;
 	PurpleXferUiOps *ui_ops;
+	PurpleXferPrivData *priv;
 
 	g_return_val_if_fail(type    != PURPLE_XFER_UNKNOWN, NULL);
 	g_return_val_if_fail(account != NULL,              NULL);
@@ -70,6 +98,11 @@
 	xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
 	xfer->fd = -1;
 
+	priv = g_new0(PurpleXferPrivData, 1);
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	g_hash_table_insert(xfers_data, xfer, priv);
+
 	ui_ops = purple_xfer_get_ui_ops(xfer);
 
 	if (ui_ops != NULL && ui_ops->new_xfer != NULL)
@@ -102,9 +135,11 @@
 	g_free(xfer->remote_ip);
 	g_free(xfer->local_filename);
 
+	g_hash_table_remove(xfers_data, xfer);
+
 	PURPLE_DBUS_UNREGISTER_POINTER(xfer);
+	xfers = g_list_remove(xfers, xfer);
 	g_free(xfer);
-	xfers = g_list_remove(xfers, xfer);
 }
 
 void
@@ -482,13 +517,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 +537,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->ui_read == NULL && ui_ops->ui_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 +559,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);
 	}
@@ -939,17 +981,23 @@
 }
 
 static void
-transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
+do_transfer(PurpleXfer *xfer)
 {
 	PurpleXferUiOps *ui_ops;
-	PurpleXfer *xfer = (PurpleXfer *)data;
 	guchar *buffer = NULL;
 	gssize r = 0;
 
-	if (condition & PURPLE_INPUT_READ) {
+	ui_ops = purple_xfer_get_ui_ops(xfer);
+
+	if (xfer->type == PURPLE_XFER_RECEIVE) {
 		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->ui_write)
+				wc = ui_ops->ui_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);
@@ -961,7 +1009,7 @@
 			g_free(buffer);
 			return;
 		}
-	} else if (condition & PURPLE_INPUT_WRITE) {
+	} else if (xfer->type == PURPLE_XFER_SEND) {
 		size_t result;
 		size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
 
@@ -975,26 +1023,53 @@
 			return;
 		}
 
-		buffer = g_malloc0(s);
+		if (ui_ops && ui_ops->ui_read) {
+			gssize tmp = ui_ops->ui_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->ui_read == NULL && ui_ops->ui_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 +1091,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));
@@ -1028,22 +1101,45 @@
 }
 
 static void
+transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleXfer *xfer = data;
+
+	if (xfer->dest_fp == NULL) {
+		/* The UI is moderating its side manually */
+		PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
+		if (0 == (priv->ready & PURPLE_XFER_READY_UI)) {
+			priv->ready |= PURPLE_XFER_READY_PRPL;
+
+			purple_input_remove(xfer->watcher);
+			xfer->watcher = 0;
+			return;
+		}
+	}
+
+	do_transfer(xfer);
+}
+
+static void
 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->ui_read == NULL && ui_ops->ui_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)
+	if (xfer->fd != -1)
 		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
 
 	xfer->start_time = time(NULL);
@@ -1068,6 +1164,54 @@
 }
 
 void
+purple_xfer_ui_ready(PurpleXfer *xfer)
+{
+	PurpleInputCondition cond;
+	PurpleXferType type;
+	PurpleXferPrivData *priv;
+
+	g_return_if_fail(xfer != NULL);
+
+	priv = g_hash_table_lookup(xfers_data, xfer);
+	priv->ready |= PURPLE_XFER_READY_UI;
+
+	if (0 == (priv->ready & PURPLE_XFER_READY_PRPL))
+		return;
+
+	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->fd != -1)
+		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
+
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	do_transfer(xfer);
+}
+
+void
+purple_xfer_prpl_ready(PurpleXfer *xfer)
+{
+	PurpleXferPrivData *priv;
+
+	g_return_if_fail(xfer != NULL);
+
+	priv = g_hash_table_lookup(xfers_data, xfer);
+	priv->ready |= PURPLE_XFER_READY_PRPL;
+
+	/* I don't think fwrite/fread are ever *not* ready */
+	if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI))
+		return;
+
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	do_transfer(xfer);
+}
+
+void
 purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
 				unsigned int port)
 {
@@ -1081,6 +1225,13 @@
 
 	purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED);
 
+	/*
+	 * FIXME 3.0.0 -- there's too much broken code depending on fd == 0
+	 * meaning "don't use a real fd"
+	 */
+	if (fd == 0)
+		fd = -1;
+
 	if (type == PURPLE_XFER_RECEIVE) {
 		cond = PURPLE_INPUT_READ;
 
@@ -1127,7 +1278,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1190,7 +1341,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1255,7 +1406,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1325,6 +1476,9 @@
 purple_xfers_init(void) {
 	void *handle = purple_xfers_get_handle();
 
+	xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+	                                   NULL, purple_xfer_priv_data_destroy);
+
 	/* register signals */
 	purple_signal_register(handle, "file-recv-accept",
 	                     purple_marshal_VOID__POINTER, NULL, 1,
@@ -1371,6 +1525,9 @@
 
 	purple_signals_disconnect_by_handle(handle);
 	purple_signals_unregister_by_instance(handle);
+
+	g_hash_table_destroy(xfers_data);
+	xfers_data = NULL;
 }
 
 void
--- a/libpurple/ft.h	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/ft.h	Wed Aug 12 10:04:54 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 (*ui_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 (*ui_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. */
@@ -548,6 +587,12 @@
  * file receive transfer. On send, @a fd must be specified, and
  * @a ip and @a port are ignored.
  *
+ * Prior to libpurple 2.6.0, passing '0' to @a fd was special-cased to
+ * allow the protocol plugin to facilitate the file transfer itself. As of
+ * 2.6.0, this is supported (for backward compatibility), but will be
+ * removed in libpurple 3.0.0. If a prpl detects that the running libpurple
+ * is running 2.6.0 or higher, it should use the invalid fd '-1'.
+ *
  * @param xfer The file transfer.
  * @param fd   The file descriptor for the socket.
  * @param ip   The IP address to connect to.
@@ -617,6 +662,28 @@
  */
 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. Used when the UI is providing
+ * read/write/data_not_sent UI ops.
+ *
+ * @param xfer The file transfer which is ready.
+ *
+ * @since 2.6.0
+ */
+void purple_xfer_ui_ready(PurpleXfer *xfer);
+
+/**
+ * Allows the prpl to signal it's readh to send/receive data (depending on
+ * the direction of the file transfer. Used when the prpl provides read/write
+ * ops and cannot/does not provide a raw fd to the core.
+ *
+ * @param xfer The file transfer which is ready.
+ *
+ * @since 2.6.0
+ */
+void purple_xfer_prpl_ready(PurpleXfer *xfer);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/protocols/jabber/buddy.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Aug 12 10:04:54 2009 +0000
@@ -513,6 +513,7 @@
 
 		xmlnode_insert_data(binval, enc, -1);
 		g_free(enc);
+		purple_imgstore_unref(img);
 	} else if (vc_node) {
 		xmlnode *photo;
 		/* TODO: Remove all PHOTO children? (see above note) */
--- a/libpurple/protocols/jabber/chat.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Wed Aug 12 10:04:54 2009 +0000
@@ -750,6 +750,7 @@
 
 	if(!server || !*server) {
 		purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL);
+		purple_roomlist_set_in_progress(js->roomlist, FALSE);
 		return;
 	}
 
--- a/libpurple/protocols/jabber/iq.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Wed Aug 12 10:04:54 2009 +0000
@@ -282,11 +282,6 @@
 	id = xmlnode_get_attrib(packet, "id");
 	iq_type = xmlnode_get_attrib(packet, "type");
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
-			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
-	if (signal_return)
-		return;
-
 	/*
 	 * child will be either the first tag child or NULL if there is no child.
 	 * Historically, we used just the 'query' subchild, but newer XEPs use
@@ -345,6 +340,11 @@
 		return;
 	}
 
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
+	if (signal_return)
+		return;
+
 	/* First, lets see if a special callback got registered */
 	if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) {
 		if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
--- a/libpurple/protocols/jabber/si.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Wed Aug 12 10:04:54 2009 +0000
@@ -67,7 +67,7 @@
 
 	JabberIBBSession *ibb_session;
 	guint ibb_timeout_handle;
-	FILE *fp;
+	PurpleCircBuffer *ibb_buffer;
 } JabberSIXfer;
 
 /* some forward declarations */
@@ -1012,18 +1012,8 @@
 	if (size <= purple_xfer_get_bytes_remaining(xfer)) {
 		purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n",
 			size);
-		if(!fwrite(data, size, 1, jsx->fp)) {
-			purple_debug_error("jabber", "error writing to file\n");
-			purple_xfer_cancel_remote(xfer);
-			return;
-		}
-		purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) + size);
-		purple_xfer_update_progress(xfer);
-
-		if (purple_xfer_get_bytes_remaining(xfer) == 0) {
-			purple_xfer_set_completed(xfer, TRUE);
-			purple_xfer_end(xfer);
-		}
+		purple_circ_buffer_append(jsx->ibb_buffer, data, size);
+		purple_xfer_prpl_ready(xfer);
 	} else {
 		/* trying to write past size of file transfers negotiated size,
 		  reject transfer to protect against malicious behaviour */
@@ -1034,6 +1024,25 @@
 
 }
 
+static gssize
+jabber_si_xfer_ibb_read(guchar **out_buffer, PurpleXfer *xfer)
+{
+	JabberSIXfer *jsx = xfer->data;
+	guchar *buffer;
+	gsize size;
+	gsize tmp;
+
+	size = jsx->ibb_buffer->bufused;
+	*out_buffer = buffer = g_malloc(size);
+	while ((tmp = purple_circ_buffer_get_max_read(jsx->ibb_buffer))) {
+		memcpy(buffer, jsx->ibb_buffer->outptr, tmp);
+		buffer += tmp;
+		purple_circ_buffer_mark_read(jsx->ibb_buffer, tmp);
+	}
+
+	return size;
+}
+
 static gboolean
 jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id,
                            xmlnode *open)
@@ -1044,21 +1053,10 @@
 		JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 		JabberIBBSession *sess =
 			jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer);
-		const char *filename;
 
 		jabber_si_bytestreams_ibb_timeout_remove(jsx);
 
 		if (sess) {
-			/* open the file to write to */
-			filename = purple_xfer_get_local_filename(xfer);
-			jsx->fp = g_fopen(filename, "wb");
-			if (jsx->fp == NULL) {
-				purple_debug_error("jabber", "failed to open file %s for writing: %s\n",
-					filename, g_strerror(errno));
-				purple_xfer_cancel_remote(xfer);
-				return FALSE;
-			}
-
 			/* setup callbacks here...*/
 			jabber_ibb_session_set_data_received_callback(sess,
 				jabber_si_xfer_ibb_recv_data_cb);
@@ -1068,9 +1066,14 @@
 				jabber_si_xfer_ibb_error_cb);
 
 			jsx->ibb_session = sess;
+			jsx->ibb_buffer =
+				purple_circ_buffer_new(jabber_ibb_session_get_block_size(sess));
+
+			/* set up read function */
+			purple_xfer_set_read_fnc(xfer, jabber_si_xfer_ibb_read);
 
 			/* start the transfer */
-			purple_xfer_start(xfer, 0, NULL, 0);
+			purple_xfer_start(xfer, -1, NULL, 0);
 			return TRUE;
 		} else {
 			/* failed to create IBB session */
@@ -1086,32 +1089,17 @@
 	}
 }
 
-static void
-jabber_si_xfer_ibb_send_data(JabberIBBSession *sess)
+static gssize
+jabber_si_xfer_ibb_write(const guchar *buffer, size_t len, PurpleXfer *xfer)
 {
-	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
 	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
-	gsize remaining = purple_xfer_get_bytes_remaining(xfer);
-	gsize packet_size = remaining < jabber_ibb_session_get_block_size(sess) ?
-		remaining : jabber_ibb_session_get_block_size(sess);
-	gpointer data = g_malloc(packet_size);
-	int res;
+	JabberIBBSession *sess = jsx->ibb_session;
+	gsize packet_size = len < jabber_ibb_session_get_block_size(sess) ?
+		len : jabber_ibb_session_get_block_size(sess);
 
-	purple_debug_info("jabber", "IBB: about to read %" G_GSIZE_FORMAT " bytes from file %p\n",
-		packet_size, jsx->fp);
-	res = fread(data, packet_size, 1, jsx->fp);
+	jabber_ibb_session_send_data(sess, buffer, packet_size);
 
-	if (res == 1) {
-		jabber_ibb_session_send_data(sess, data, packet_size);
-		purple_xfer_set_bytes_sent(xfer,
-			purple_xfer_get_bytes_sent(xfer) + packet_size);
-		purple_xfer_update_progress(xfer);
-	} else {
-		purple_debug_error("jabber",
-			"jabber_si_xfer_ibb_send_data: error reading from file\n");
-		purple_xfer_cancel_local(xfer);
-	}
-	g_free(data);
+	return packet_size;
 }
 
 static void
@@ -1127,7 +1115,7 @@
 		purple_xfer_end(xfer);
 	} else {
 		/* send more... */
-		jabber_si_xfer_ibb_send_data(sess);
+		purple_xfer_prpl_ready(xfer);
 	}
 }
 
@@ -1135,28 +1123,13 @@
 jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess)
 {
 	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
-	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 	JabberStream *js = jabber_ibb_session_get_js(sess);
 	PurpleConnection *gc = js->gc;
 	PurpleAccount *account = purple_connection_get_account(gc);
 
 	if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
-		const char *filename = purple_xfer_get_local_filename(xfer);
-		jsx->fp = g_fopen(filename, "rb");
-		if (jsx->fp == NULL) {
-			purple_debug_error("jabber", "Failed to open file %s for reading: %s\n",
-				filename, g_strerror(errno));
-			purple_xfer_error(purple_xfer_get_type(xfer), account,
-				jabber_ibb_session_get_who(sess),
-				_("Failed to open the file"));
-			purple_xfer_cancel_local(xfer);
-			return;
-		}
-
-		purple_xfer_start(xfer, 0, NULL, 0);
-		purple_xfer_set_bytes_sent(xfer, 0);
-		purple_xfer_update_progress(xfer);
-		jabber_si_xfer_ibb_send_data(sess);
+		purple_xfer_start(xfer, -1, NULL, 0);
+		purple_xfer_prpl_ready(xfer);
 	} else {
 		/* error */
 		purple_xfer_error(purple_xfer_get_type(xfer), account,
@@ -1171,8 +1144,6 @@
 {
 	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 
-	purple_xfer_ref(xfer);
-
 	jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id,
 		purple_xfer_get_remote_user(xfer), xfer);
 
@@ -1187,6 +1158,11 @@
 		jabber_ibb_session_set_error_callback(jsx->ibb_session,
 			jabber_si_xfer_ibb_error_cb);
 
+		purple_xfer_set_write_fnc(xfer, jabber_si_xfer_ibb_write);
+
+		jsx->ibb_buffer =
+			purple_circ_buffer_new(jabber_ibb_session_get_block_size(jsx->ibb_session));
+
 		/* open the IBB session */
 		jabber_ibb_session_open(jsx->ibb_session);
 
@@ -1342,10 +1318,8 @@
 			jabber_ibb_session_destroy(jsx->ibb_session);
 		}
 
-		if (jsx->fp) {
-			purple_debug_info("jabber",
-				"jabber_si_xfer_free: closing file for IBB transfer\n");
-			fclose(jsx->fp);
+		if (jsx->ibb_buffer) {
+			purple_circ_buffer_destroy(jsx->ibb_buffer);
 		}
 
 		purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx);
@@ -1356,7 +1330,6 @@
 		g_free(jsx->rxqueue);
 		g_free(jsx);
 		xfer->data = NULL;
-
 	}
 }
 
@@ -1632,7 +1605,6 @@
 		jsx->local_streamhost_fd = -1;
 
 		jsx->ibb_session = NULL;
-		jsx->fp = NULL;
 
 		purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
 		purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
--- a/libpurple/protocols/msn/msg.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/msn/msg.c	Wed Aug 12 10:04:54 2009 +0000
@@ -1102,6 +1102,7 @@
 {
 	GHashTable *body;
 	const gchar *guid;
+	gboolean accepted = FALSE;
 
 	g_return_if_fail(cmdproc != NULL);
 	g_return_if_fail(msg != NULL);
@@ -1129,6 +1130,9 @@
 		} else
 			purple_debug_warning("msn", "Invite msg missing "
 					"Application-GUID.\n");
+
+		accepted = TRUE;
+
 	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
 		purple_debug_info("msn", "Computer call\n");
 
@@ -1154,9 +1158,35 @@
 				g_free(buf);
 			}
 		}
-	} else
-		purple_debug_warning("msn",
-				"Unhandled invite msg with GUID %s.\n", guid);
+	} else {
+		const gchar *application = g_hash_table_lookup(body, "Application-Name");
+		purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
+		                     guid, application ? application : "(null)");
+	}
+
+	if (!accepted) {
+		const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie");
+		if (cookie) {
+			MsnSwitchBoard *swboard = cmdproc->data;
+			char *text;
+			MsnMessage *cancel;
+
+			cancel = msn_message_new(MSN_MSG_TEXT);
+			msn_message_set_content_type(cancel, "text/x-msmsgsinvite");
+			msn_message_set_charset(cancel, "UTF-8");
+			msn_message_set_flag(cancel, 'U');
+
+			text = g_strdup_printf("Invitation-Command: CANCEL\r\n"
+			                       "Invitation-Cookie: %s\r\n"
+			                       "Cancel-Code: REJECT_NOT_INSTALLED\r\n",
+			                       cookie);
+			msn_message_set_bin_data(cancel, text, strlen(text));
+			g_free(text);
+
+			msn_switchboard_send_msg(swboard, cancel, TRUE);
+			msn_message_destroy(cancel);
+		}
+	}
 
 	g_hash_table_destroy(body);
 }
--- a/libpurple/protocols/msn/slp.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/msn/slp.c	Wed Aug 12 10:04:54 2009 +0000
@@ -244,6 +244,8 @@
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
 {
+	gboolean accepted = FALSE;
+
 	if (!strcmp(euf_guid, MSN_OBJ_GUID))
 	{
 		/* Emoticon or UserDisplay */
@@ -314,7 +316,10 @@
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
 		purple_imgstore_unref(img);
+
+		accepted = TRUE;
 	}
+
 	else if (!strcmp(euf_guid, MSN_FT_GUID))
 	{
 		/* File Transfer */
@@ -360,6 +365,9 @@
 
 			purple_xfer_request(xfer);
 		}
+
+		accepted = TRUE;
+
 	} else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) {
 		purple_debug_info("msn", "Cam request.\n");
 		if (slpcall && slpcall->slplink &&
@@ -382,6 +390,7 @@
 				g_free(buf);
 			}
 		}
+
 	} else if (!strcmp(euf_guid, MSN_CAM_GUID)) {
 		purple_debug_info("msn", "Cam invite.\n");
 		if (slpcall && slpcall->slplink &&
@@ -404,8 +413,16 @@
 				g_free(buf);
 			}
 		}
+
 	} else
 		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
+
+	if (!accepted) {
+		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+		                                slpcall->session_id);
+		send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
+		g_free(content);
+	}
 }
 
 void
--- a/libpurple/protocols/msn/slplink.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/msn/slplink.c	Wed Aug 12 10:04:54 2009 +0000
@@ -456,7 +456,7 @@
 	slpmsg->info = "SLP FILE";
 
 	xfer = (PurpleXfer *)slpcall->xfer;
-	purple_xfer_start(slpcall->xfer, 0, NULL, 0);
+	purple_xfer_start(slpcall->xfer, -1, NULL, 0);
 	slpmsg->fp = xfer->dest_fp;
 	if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0)
 		slpmsg->size = st.st_size;
@@ -537,7 +537,7 @@
 					if (xfer != NULL)
 					{
 						purple_xfer_ref(xfer);
-						purple_xfer_start(xfer,	0, NULL, 0);
+						purple_xfer_start(xfer,	-1, NULL, 0);
 
 						if (xfer->data == NULL) {
 							purple_xfer_unref(xfer);
--- a/libpurple/protocols/msnp9/slplink.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/libpurple/protocols/msnp9/slplink.c	Wed Aug 12 10:04:54 2009 +0000
@@ -496,7 +496,7 @@
 	slpmsg->info = "SLP FILE";
 #endif
 	xfer = (PurpleXfer *)slpcall->xfer;
-	purple_xfer_start(slpcall->xfer, 0, NULL, 0);
+	purple_xfer_start(slpcall->xfer, -1, NULL, 0);
 	slpmsg->fp = xfer->dest_fp;
 	if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0)
 		slpmsg->size = st.st_size;
@@ -561,7 +561,7 @@
 					if (xfer != NULL)
 					{
 						purple_xfer_ref(xfer);
-						purple_xfer_start(xfer,	0, NULL, 0);
+						purple_xfer_start(xfer,	-1, NULL, 0);
 
 						if (xfer->data == NULL) {
 							purple_xfer_unref(xfer);
--- a/pidgin/gtkimhtml.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/pidgin/gtkimhtml.c	Wed Aug 12 10:04:54 2009 +0000
@@ -2953,7 +2953,7 @@
 							font->size = oldfont->size;
 						else
 							font->size = 3;
-						if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
+						if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3)))
 							gtk_imhtml_font_set_size(imhtml, font->size);
 						g_free(size);
 						fonts = g_slist_prepend (fonts, font);
--- a/pidgin/gtkmedia.c	Wed Aug 12 10:03:47 2009 +0000
+++ b/pidgin/gtkmedia.c	Wed Aug 12 10:04:54 2009 +0000
@@ -500,7 +500,7 @@
 
 	gtkmedia->priv->request_type = PURPLE_MEDIA_NONE;
 
-	purple_request_accept_cancel(gtkmedia, "Incoming Call",
+	purple_request_accept_cancel(gtkmedia, _("Incoming Call"),
 			message, NULL, PURPLE_DEFAULT_ACTION_NONE,
 			(void*)account, gtkmedia->priv->screenname, NULL,
 			gtkmedia->priv->media,
--- a/po/nn.po	Wed Aug 12 10:03:47 2009 +0000
+++ b/po/nn.po	Wed Aug 12 10:04:54 2009 +0000
@@ -2,8 +2,8 @@
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-02 23:34-0700\n"
-"PO-Revision-Date: 2009-08-01 13:29+0100\n"
+"POT-Creation-Date: 2009-08-09 19:06-0700\n"
+"PO-Revision-Date: 2009-08-09 02:41+0100\n"
 "Last-Translator: Yngve Spjeld Landro <nynorsk@landro.net>\n"
 "Language-Team: \n"
 "MIME-Version: 1.0\n"
@@ -12132,6 +12132,9 @@
 msgid "%s wishes to start a video session with you."
 msgstr "%s ønskjer å starta ei biletøkt med deg."
 
+msgid "_Pause"
+msgstr "_Pause"
+
 #, c-format
 msgid "%s has %d new message."
 msgid_plural "%s has %d new messages."
@@ -12673,7 +12676,7 @@
 "(%s for filnamn)"
 
 msgid "M_ute sounds"
-msgstr "&Demp lydar"
+msgstr "_Demp lydar"
 
 msgid "Sounds when conversation has _focus"
 msgstr "Lydar medan samtalevindauget har _fokus"
@@ -12688,7 +12691,7 @@
 msgstr "Spel"
 
 msgid "_Browse..."
-msgstr "Bla &gjennom…"
+msgstr "Bla _gjennom…"
 
 msgid "_Reset"
 msgstr "_Nullstill"
@@ -13068,9 +13071,6 @@
 msgid "_Open Mail"
 msgstr "_Opna e-post"
 
-msgid "_Pause"
-msgstr "_Pause"
-
 msgid "_Edit"
 msgstr "_Endra"