libpurple/protocols/irc/irc.c

branch
release-2.x.y
changeset 41335
c49dcf00bee6
parent 41333
ed2b25ccdf2c
child 41339
3230f4408394
--- a/libpurple/protocols/irc/irc.c	Sat Apr 23 05:05:15 2022 -0500
+++ b/libpurple/protocols/irc/irc.c	Sat Apr 23 05:05:54 2022 -0500
@@ -29,6 +29,7 @@
 #include "blist.h"
 #include "conversation.h"
 #include "debug.h"
+#include "glibcompat.h"
 #include "notify.h"
 #include "prpl.h"
 #include "plugin.h"
@@ -95,9 +96,21 @@
 		ret = write(irc->fd, buf, len);
 	}
 
+	irc->send_time = time(NULL);
+
 	return ret;
 }
 
+void irc_priority_send(struct irc_conn *irc, const char *buf)
+{
+	if(irc->sent_partial) {
+		g_queue_insert_after(irc->send_queue, irc->send_queue->head,
+		                     g_strdup(buf));
+	} else {
+		do_send(irc, buf, strlen(buf));
+	}
+}
+
 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
 {
 	struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
@@ -108,99 +121,125 @@
 	return len;
 }
 
-static void
-irc_send_cb(gpointer data, gint source, PurpleInputCondition cond)
-{
-	struct irc_conn *irc = data;
-	int ret, writelen;
+static gboolean
+irc_send_handler_cb(gpointer data) {
+	struct irc_conn *irc = (struct irc_conn *)data;
+	gint available = 0;
+	gint interval = 0;
+
+	interval = purple_account_get_int(irc->account, "ratelimit-interval",
+	                                  IRC_DEFAULT_COMMAND_INTERVAL);
 
-	writelen = purple_circ_buffer_get_max_read(irc->outbuf);
-
-	if (writelen == 0) {
-		purple_input_remove(irc->writeh);
-		irc->writeh = 0;
-		return;
+	/* Check if we're enabled. */
+	if(interval < 1) {
+		available = G_MAXINT;
+	} else {
+		gint burst = purple_account_get_int(irc->account, "ratelimit-burst",
+		                                    IRC_DEFAULT_COMMAND_MAX_BURST);
+		available = (time(NULL) - irc->send_time) / interval;
+		if(available > burst) {
+			available = burst;
+		}
 	}
 
-	ret = do_send(irc, irc->outbuf->outptr, writelen);
+	while(available > 0) {
+		gchar *msg = NULL;
+		gpointer raw = NULL;
+		gint length = 0, ret = 0;
+
+		/* No message in the queue should be NULL, so a NULL value means the
+		 * queue is empty.
+		 */
+		raw = g_queue_pop_head(irc->send_queue);
+		if(raw == NULL) {
+			break;
+		}
+
+		msg = (gchar *)raw;
+		length = strlen(msg);
+
+		ret = do_send(irc, msg, length);
+		if(ret <= 0 && errno != EAGAIN) {
+			PurpleConnection *gc = purple_account_get_connection(irc->account);
+			gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
+			                             g_strerror(errno));
+
+			purple_connection_error_reason(gc,
+			                               PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			                               tmp);
+			g_free(tmp);
+
+			g_free(msg);
+
+			irc->send_handler = 0;
+
+			return G_SOURCE_REMOVE;
+		} else if(ret < length) {
+			gchar *partial = NULL;
 
-	if (ret < 0 && errno == EAGAIN)
-		return;
-	else if (ret <= 0) {
-		PurpleConnection *gc = purple_account_get_connection(irc->account);
-		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
-			g_strerror(errno));
-		purple_connection_error_reason (gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-		g_free(tmp);
-		return;
+			/* The preceeding conditional allows EAGAIN to fall through to
+			 * here so that we can retransmit it.  There shouldn't even be a
+			 * case where ret < 0 and != EAGAIN, which is why we have the
+			 * assert.
+			 */
+			if(ret < 0) {
+				if(ret == EAGAIN) {
+					ret = 0;
+				} else {
+					g_assert_not_reached();
+				}
+			}
+
+			/* We need to move past the characters we already wrote and requeue
+			 * the rest of the string. We know ret is less than length, so the
+			 * starting address of msg plus ret can never get outside of the
+			 * string, and likewise, length minus ret will always be < length
+			 * because ret is less than length and if it was somehow negative,
+			 * it has been reset to zero.
+			 */
+			partial = g_strndup(msg + ret, length - ret);
+
+			/* requeue the item to the start of the queue */
+			g_queue_push_head(irc->send_queue, partial);
+			irc->sent_partial = TRUE;
+		} else {
+			/* We successfully sent a message so decrement the counter. */
+			available -= 1;
+			irc->sent_partial = FALSE;
+		}
+
+		/* Message was processed successfully or a partial message was
+		 * allocated and requeued so we can free the one we popped off.
+		 */
+		g_free(msg);
 	}
 
-	purple_circ_buffer_mark_read(irc->outbuf, ret);
-
-#if 0
-	/* We *could* try to write more if we wrote it all */
-	if (ret == write_len) {
-		irc_send_cb(data, source, cond);
-	}
-#endif
+	return G_SOURCE_CONTINUE;
 }
 
-int irc_send(struct irc_conn *irc, const char *buf)
+void irc_send(struct irc_conn *irc, const char *buf)
 {
     return irc_send_len(irc, buf, strlen(buf));
 }
 
-int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
-{
-	int ret;
- 	char *tosend = g_strdup(buf);
+void
+irc_send_len(struct irc_conn *irc, const char *buf, int buflen) {
+	char *tosend = g_strdup(buf);
 
 	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
 
-	if (tosend == NULL)
-		return 0;
-
-	if (!purple_strequal(tosend, buf)) {
-		buflen = strlen(tosend);
+	if(tosend == NULL) {
+		return;
 	}
 
-	if (purple_debug_is_verbose()) {
+	if(purple_debug_is_verbose()) {
 		char *clean = purple_utf8_salvage(tosend);
 		clean = g_strstrip(clean);
 		purple_debug_misc("irc", "<< %s\n", clean);
 		g_free(clean);
 	}
 
-	/* If we're not buffering writes, try to send immediately */
-	if (!irc->writeh)
-		ret = do_send(irc, tosend, buflen);
-	else {
-		ret = -1;
-		errno = EAGAIN;
-	}
-
-	/* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s",
-		irc->gsc ? " (ssl)" : "", tosend); */
-	if (ret <= 0 && errno != EAGAIN) {
-		PurpleConnection *gc = purple_account_get_connection(irc->account);
-		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
-			g_strerror(errno));
-		purple_connection_error_reason (gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-		g_free(tmp);
-	} else if (ret < buflen) {
-		if (ret < 0)
-			ret = 0;
-		if (!irc->writeh)
-			irc->writeh = purple_input_add(
-				irc->gsc ? irc->gsc->fd : irc->fd,
-				PURPLE_INPUT_WRITE, irc_send_cb, irc);
-		purple_circ_buffer_append(irc->outbuf, tosend + ret,
-			buflen - ret);
-	}
-	g_free(tosend);
-	return ret;
+	g_queue_push_tail(irc->send_queue, tosend);
 }
 
 /* XXX I don't like messing directly with these buddies */
@@ -357,7 +396,9 @@
 	gc->proto_data = irc = g_new0(struct irc_conn, 1);
 	irc->fd = -1;
 	irc->account = account;
-	irc->outbuf = purple_circ_buffer_new(512);
+
+	irc->send_queue = g_queue_new();
+	irc->sent_partial = FALSE;
 
 	userparts = g_strsplit(username, "@", 2);
 	purple_connection_set_display_name(gc, userparts[0]);
@@ -406,6 +447,7 @@
 	const char *nickname, *identname, *realname;
 	struct irc_conn *irc = gc->proto_data;
 	const char *pass = purple_connection_get_password(gc);
+	gint interval, burst;
 #ifdef HAVE_CYRUS_SASL
 	const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE);
 #endif
@@ -417,7 +459,7 @@
 		else /* intended to fall through */
 #endif
 			buf = irc_format(irc, "v:", "PASS", pass);
-		if (irc_send(irc, buf) < 0) {
+		if (do_send(irc, buf, strlen(buf)) < 0) {
 			g_free(buf);
 			return FALSE;
 		}
@@ -450,7 +492,7 @@
 	                 strlen(realname) ? realname : nickname);
 	g_free(tmp);
 	g_free(server);
-	if (irc_send(irc, buf) < 0) {
+	if (do_send(irc, buf, strlen(buf)) < 0) {
 		g_free(buf);
 		return FALSE;
 	}
@@ -459,7 +501,7 @@
 	buf = irc_format(irc, "vn", "NICK", nickname);
 	irc->reqnick = g_strdup(nickname);
 	irc->nickused = FALSE;
-	if (irc_send(irc, buf) < 0) {
+	if (do_send(irc, buf, strlen(buf)) < 0) {
 		g_free(buf);
 		return FALSE;
 	}
@@ -467,6 +509,15 @@
 
 	irc->recv_time = time(NULL);
 
+	/* Give ourselves one full burst for startup commands. */
+	interval = purple_account_get_int(irc->account, "ratelimit-interval",
+	                                  IRC_DEFAULT_COMMAND_INTERVAL);
+	burst = purple_account_get_int(irc->account, "ratelimit-burst",
+	                               IRC_DEFAULT_COMMAND_MAX_BURST);
+
+	irc->send_time = time(NULL) - (interval * burst);
+	irc->send_handler = g_timeout_add_seconds(1, irc_send_handler_cb, irc);
+
 	return TRUE;
 }
 
@@ -541,10 +592,10 @@
 		g_string_free(irc->motd, TRUE);
 	g_free(irc->server);
 
-	if (irc->writeh)
-		purple_input_remove(irc->writeh);
-
-	purple_circ_buffer_destroy(irc->outbuf);
+	g_queue_free_full(irc->send_queue, g_free);
+	if(irc->send_handler != 0) {
+		g_source_remove(irc->send_handler);
+	}
 
 	g_free(irc->mode_chars);
 	g_free(irc->reqnick);
@@ -1108,6 +1159,16 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 #endif
 
+	option = purple_account_option_int_new(_("Seconds between sending messages"),
+	                                       "ratelimit-interval",
+	                                       IRC_DEFAULT_COMMAND_INTERVAL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_int_new(_("Maximum messages to send at once"),
+	                                       "ratelimit-burst",
+	                                       IRC_DEFAULT_COMMAND_MAX_BURST);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
 	_irc_plugin = plugin;
 
 	purple_prefs_remove("/plugins/prpl/irc/quitmsg");

mercurial