# HG changeset patch
# Node ID 7c84503b4e53fba4790baf40aaebc8c0c312e4ad
# Parent  abfe2e6b4698fc6a14f17f6be54def5b7224cd14
diff --git a/ibis/ibisclient.c b/ibis/ibisclient.c
--- a/ibis/ibisclient.c
+++ b/ibis/ibisclient.c
@@ -26,6 +26,7 @@
 
 #include "ibisclient.h"
 
+#include "ibisbatch.h"
 #include "ibischanmodes.h"
 #include "ibisconstants.h"
 #include "ibissource.h"
@@ -55,6 +56,7 @@
 	SIG_MESSAGE,
 	SIG_WRITING_MESSAGE,
 	SIG_WROTE_MESSAGE,
+	SIG_BATCH,
 	N_SIGNALS,
 };
 static guint signals[N_SIGNALS] = {0, };
@@ -63,6 +65,8 @@
 	GObject parent;
 
 	GCancellable *cancellable;
+
+	GHashTable *batches;
 	IbisCapabilities *capabilities;
 	IbisChanmodes *chanmodes;
 	gboolean connected;
@@ -264,7 +268,6 @@
 	}
 }
 
-
 static void
 ibis_client_register(IbisClient *client, const char *password) {
 	IbisMessage *message = NULL;
@@ -291,10 +294,106 @@
 	ibis_client_attempt_nick(client);
 }
 
+static gboolean
+ibis_client_emit_message(IbisClient *client, IbisMessage *message) {
+	gboolean handled = FALSE;
+
+	g_return_val_if_fail(IBIS_IS_CLIENT(client), FALSE);
+	g_return_val_if_fail(IBIS_IS_MESSAGE(message), FALSE);
+
+	g_signal_emit(client,
+	              signals[SIG_MESSAGE],
+	              ibis_message_get_command_quark(message),
+	              ibis_message_get_command(message),
+	              message,
+	              &handled);
+
+	return handled;
+}
+
+static gboolean
+ibis_client_handle_batch(IbisClient *client, IbisMessage *message) {
+	IbisBatch *batch = NULL;
+	const char *reference_tag = NULL;
+	gboolean handled = FALSE;
+
+	reference_tag = ibis_batch_is_end(message);
+
+	/* No reference tag means this is a new batch, so create it and move on. */
+	if(ibis_str_is_empty(reference_tag)) {
+		GError *error = NULL;
+
+		batch = ibis_batch_parse(message, &error);
+		if(error != NULL) {
+			g_warning("failed to parse batch message: %s", error->message);
+
+			return TRUE;
+		}
+
+		reference_tag = ibis_batch_get_reference_tag(batch);
+
+		/* Add the batch to our collection and give it our reference. */
+		g_hash_table_insert(client->batches, g_strdup(reference_tag), batch);
+
+		return TRUE;
+	}
+
+	/* We have a reference tag so we have some work to do. */
+	batch = g_hash_table_lookup(client->batches, reference_tag);
+	if(!IBIS_IS_BATCH(batch)) {
+		g_warning("failed to find a batch with reference tag '%s'",
+		          reference_tag);
+
+		return TRUE;
+	}
+
+	/* Emit the batch signal. */
+	g_signal_emit(client, signals[SIG_BATCH],
+	              g_quark_from_string(reference_tag), batch, &handled);
+
+	/* If no one handled the batch, we emit the message signal for each message
+	 * individually.
+	 */
+	if(!handled) {
+		GListModel *model = G_LIST_MODEL(batch);
+		guint n_messages = g_list_model_get_n_items(model);
+
+		g_warning("batch '%s' was not handled, replaying manually",
+		          reference_tag);
+
+		for(guint i = 0; i < n_messages; i++) {
+			IbisMessage *message = g_list_model_get_item(model, i);
+
+			handled = ibis_client_emit_message(client, message);
+			if(!handled) {
+				g_warning("unhandled message: '%s'",
+				          ibis_message_get_raw_message(message));
+			}
+
+			g_clear_object(&message);
+		}
+
+		g_warning("finished manually replaying batch '%s'", reference_tag);
+	}
+
+	if(!g_hash_table_remove(client->batches, reference_tag)) {
+		g_warning("failed to remove batch '%s'", reference_tag);
+	}
+
+	return TRUE;
+}
+
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
 static void
+ibis_client_capabilities_ready_cb(IbisCapabilities *capabilities,
+                                  G_GNUC_UNUSED gpointer data)
+{
+	ibis_capabilities_request(capabilities, IBIS_CAPABILITY_BATCH);
+}
+
+static void
 ibis_client_write_cb(GObject *source, GAsyncResult *result, gpointer data) {
 	IbisMessage *message = data;
 	IbisClient *client = g_object_get_data(G_OBJECT(message), "ibis-client");
@@ -365,9 +464,12 @@
 
 		g_clear_error(&error);
 	} else if(IBIS_IS_MESSAGE(message)) {
+		IbisTags *tags = NULL;
 		GQuark quark = 0;
+		const char *reference_tag = NULL;
 		const char *command = NULL;
 		const char *source = NULL;
+		gboolean batched = FALSE;
 		gboolean handled = FALSE;
 
 		/* We use the quark as we know it's been normalized. */
@@ -424,11 +526,39 @@
 			g_free(nick);
 		}
 
-		g_signal_emit(client, signals[SIG_MESSAGE], quark, command, message,
-		              &handled);
+		tags = ibis_message_get_tags(message);
+		reference_tag = ibis_tags_lookup(tags, IBIS_TAG_BATCH);
+		if(!ibis_str_is_empty(reference_tag)) {
+			IbisBatch *batch = NULL;
+
+			batch = g_hash_table_lookup(client->batches, reference_tag);
+			if(IBIS_IS_BATCH(batch)) {
+				GError *error = NULL;
+				gboolean added = FALSE;
+
+				added = ibis_batch_add_message(batch, message, &error);
+				if(error != NULL) {
+					g_warning("failed to add message to batch: %s",
+					          error->message);
+					g_clear_error(&error);
+				}
 
-		if(!handled) {
-			g_warning("unhandled message: '%s'", line);
+				if(added) {
+					batched = TRUE;
+				}
+			}
+		}
+
+		if(!batched) {
+			handled = ibis_client_emit_message(client, message);
+
+			if(ibis_str_equal(command, IBIS_MSG_BATCH)) {
+				handled = ibis_client_handle_batch(client, message);
+			}
+
+			if(!handled) {
+				g_warning("unhandled message: '%s'", line);
+			}
 		}
 	} else {
 		g_warning("parsing failed to generate a message or error for '%s'",
@@ -644,6 +774,8 @@
 	}
 
 	g_clear_object(&client->cancellable);
+
+	g_clear_pointer(&client->batches, g_hash_table_unref);
 	g_clear_object(&client->capabilities);
 	g_clear_object(&client->chanmodes);
 	g_clear_error(&client->error);
@@ -752,11 +884,16 @@
 	client->message_tags_negotiated = FALSE;
 
 	client->capabilities = ibis_capabilities_new();
+	g_signal_connect_object(client->capabilities, "ready",
+	                        G_CALLBACK(ibis_client_capabilities_ready_cb),
+	                        client, G_CONNECT_DEFAULT);
 	g_signal_connect(client->capabilities,
 	                 "ack::" IBIS_CAPABILITY_MESSAGE_TAGS,
 	                 G_CALLBACK(ibis_client_ack_message_tags_cb),
 	                 NULL);
 
+	client->batches = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+	                                        g_object_unref);
 	client->chanmodes = ibis_chanmodes_new();
 
 	client->features = ibis_features_new();
@@ -1047,6 +1184,33 @@
 		G_TYPE_NONE,
 		1,
 		IBIS_TYPE_MESSAGE);
+
+	/**
+	 * IbisClient::batch:
+	 * @client: the instance
+	 * @batch: the batch
+	 *
+	 * Emitted when the server have finished sending a batch.
+	 *
+	 * This signal supports *details* which is the [property@Batch:batch-type]
+	 * the batch.
+	 *
+	 * Returns: true to stop other handlers from being invoked; false
+	 *          otherwise.
+	 *
+	 * Since: 0.12
+	 */
+	signals[SIG_BATCH] = g_signal_new_class_handler(
+		"batch",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
+		G_CALLBACK(ibis_client_default_message_handler),
+		g_signal_accumulator_true_handled,
+		NULL,
+		NULL,
+		G_TYPE_BOOLEAN,
+		1,
+		IBIS_TYPE_BATCH);
 }
 
 /******************************************************************************
