# HG changeset patch
# Node ID 0d87c4c0e54d
# Parent  82c861678f39
diff --git a/libpurple/protocols/ircv3/meson.build b/libpurple/protocols/ircv3/meson.build
--- a/libpurple/protocols/ircv3/meson.build
+++ b/libpurple/protocols/ircv3/meson.build
@@ -1,4 +1,6 @@
 IRCV3_SOURCES = [
+	'purpleircv3connection.c',
+	'purpleircv3connection.h',
 	'purpleircv3core.c',
 	'purpleircv3core.h',
 	'purpleircv3parser.c',
diff --git a/libpurple/protocols/ircv3/purpleircv3connection.h b/libpurple/protocols/ircv3/purpleircv3connection.h
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/ircv3/purpleircv3connection.h
@@ -0,0 +1,52 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef PURPLE_IRCV3_CONNECTION_H
+#define PURPLE_IRCV3_CONNECTION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gplugin.h>
+#include <gplugin-native.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_IRCV3_TYPE_CONNECTION (purple_ircv3_connection_get_type())
+G_DECLARE_FINAL_TYPE(PurpleIRCv3Connection, purple_ircv3_connection,
+                     PURPLE_IRCV3, CONNECTION, GObject)
+
+G_GNUC_INTERNAL void purple_ircv3_connection_register(GPluginNativePlugin *plugin);
+
+G_GNUC_INTERNAL PurpleIRCv3Connection *purple_ircv3_connection_new(PurpleAccount *account);
+
+G_GNUC_INTERNAL PurpleAccount *purple_ircv3_connection_get_account(PurpleIRCv3Connection *connection);
+
+G_GNUC_INTERNAL GCancellable *purple_ircv3_connection_get_cancellable(PurpleIRCv3Connection *connection);
+
+G_GNUC_INTERNAL gboolean purple_ircv3_connection_valid(PurpleIRCv3Connection *connection, GError **error);
+
+G_GNUC_INTERNAL void purple_ircv3_connection_connect(PurpleIRCv3Connection *connection);
+
+G_GNUC_INTERNAL void purple_ircv3_connection_close(PurpleIRCv3Connection *connection);
+
+G_END_DECLS
+
+#endif /* PURPLE_IRCV3_CONNECTION_H */
diff --git a/libpurple/protocols/ircv3/purpleircv3connection.c b/libpurple/protocols/ircv3/purpleircv3connection.c
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/ircv3/purpleircv3connection.c
@@ -0,0 +1,406 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "purpleircv3connection.h"
+
+#include "purpleircv3core.h"
+
+enum {
+	PROP_0,
+	PROP_ACCOUNT,
+	PROP_CANCELLABLE,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+struct _PurpleIRCv3Connection {
+	GObject parent;
+
+	GError *validate_error;
+	PurpleAccount *account;
+
+	GSocketConnection *connection;
+	GCancellable *cancellable;
+
+	gchar *server_name;
+
+	GDataInputStream *input;
+	PurpleQueuedOutputStream *output;
+};
+
+G_DEFINE_DYNAMIC_TYPE(PurpleIRCv3Connection, purple_ircv3_connection,
+                      G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_ircv3_connection_set_account(PurpleIRCv3Connection *connection,
+                                    PurpleAccount *account)
+{
+	g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
+
+	if(g_set_object(&connection->account, account)) {
+		g_object_notify_by_pspec(G_OBJECT(connection),
+		                         properties[PROP_ACCOUNT]);
+	}
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+purple_ircv3_connection_read_cb(GObject *source, GAsyncResult *result,
+                                gpointer data)
+{
+	PurpleConnection *purple_connection = data;
+	GDataInputStream *istream = G_DATA_INPUT_STREAM(source);
+	GError *error = NULL;
+	gchar *line = NULL;
+	gsize length;
+
+	line = g_data_input_stream_read_line_finish(istream, result, &length,
+	                                            &error);
+	if(line == NULL || error != NULL) {
+		if(error == NULL) {
+			g_set_error_literal(&error, PURPLE_CONNECTION_ERROR,
+			                    PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			                    _("Server closed the connection"));
+		} else {
+			g_prefix_error(&error, "%s", _("Lost connection with server: "));
+		}
+
+		purple_connection_take_error(purple_connection, error);
+
+		/* In the off chance that line was returned, make sure we free it. */
+		g_free(line);
+
+		return;
+	}
+
+	g_message("received line: %s", line);
+
+	g_free(line);
+}
+
+static void
+purple_ircv3_connection_connected_cb(GObject *source, GAsyncResult *result,
+                                     gpointer data)
+{
+	PurpleIRCv3Connection *connection = NULL;
+	PurpleConnection *purple_connection = data;
+	GError *error = NULL;
+	GInputStream *istream = NULL;
+	GOutputStream *ostream = NULL;
+	GSocketClient *client = G_SOCKET_CLIENT(source);
+	GSocketConnection *conn = NULL;
+
+	/* Finish the async method. */
+	conn = g_socket_client_connect_to_host_finish(client, result, &error);
+	if(conn == NULL || error != NULL) {
+		g_prefix_error(&error, "%s", _("Unable to connect: "));
+
+		purple_connection_take_error(purple_connection, error);
+
+		return;
+	}
+
+	/* Grab our protocol specific connection data. */
+	connection = g_object_get_data(G_OBJECT(purple_connection),
+	                               PURPLE_IRCV3_CONNECTION_KEY);
+
+	g_message("Successfully connected to %s", connection->server_name);
+
+	/* Save our connection and setup our input and outputs. */
+	connection->connection = conn;
+
+	ostream = g_io_stream_get_output_stream(G_IO_STREAM(conn));
+	connection->output = purple_queued_output_stream_new(ostream);
+
+	istream = g_io_stream_get_input_stream(G_IO_STREAM(conn));
+	connection->input = g_data_input_stream_new(istream);
+
+	/* Add our read callback. */
+	g_data_input_stream_read_line_async(connection->input,
+	                                    G_PRIORITY_DEFAULT,
+	                                    connection->cancellable,
+	                                    purple_ircv3_connection_read_cb,
+	                                    purple_connection);
+
+	/* TODO: start login procedure. */
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_ircv3_connection_get_property(GObject *obj, guint param_id,
+                                     GValue *value, GParamSpec *pspec)
+{
+	PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			g_value_set_object(value,
+			                   purple_ircv3_connection_get_account(connection));
+			break;
+		case PROP_CANCELLABLE:
+			g_value_set_object(value,
+			                   purple_ircv3_connection_get_cancellable(connection));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_ircv3_connection_set_property(GObject *obj, guint param_id,
+                                     const GValue *value, GParamSpec *pspec)
+{
+	PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			purple_ircv3_connection_set_account(connection,
+			                                    g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_ircv3_connection_dispose(GObject *obj) {
+	PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
+
+	g_clear_object(&connection->account);
+	g_clear_object(&connection->cancellable);
+
+	g_clear_object(&connection->input);
+	g_clear_object(&connection->output);
+	g_clear_object(&connection->connection);
+
+	G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->dispose(obj);
+}
+
+static void
+purple_ircv3_connection_finalize(GObject *obj) {
+	PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
+
+	g_clear_error(&connection->validate_error);
+	g_clear_pointer(&connection->server_name, g_free);
+
+	G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->finalize(obj);
+}
+
+static void
+purple_ircv3_connection_constructed(GObject *obj) {
+	PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
+	PurpleConnection *purple_connection = NULL;
+	gchar **userparts = NULL;
+	const gchar *username = NULL;
+
+	G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->constructed(obj);
+
+	purple_connection = purple_account_get_connection(connection->account);
+
+	/* Make sure the username (which includes the servername via usersplits),
+	 * does not contain any whitespace.
+	 */
+	username = purple_account_get_username(connection->account);
+	if(strpbrk(username, " \t\v\r\n") != NULL) {
+		g_set_error(&connection->validate_error,
+		            PURPLE_CONNECTION_ERROR,
+		            PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+		            _("IRC nick and server may not contain whitespace"));
+
+		return;
+	}
+
+	/* Split the username into nick and server and store the values. */
+	userparts = g_strsplit(username, "@", 2);
+	purple_connection_set_display_name(purple_connection, userparts[0]);
+	connection->server_name = g_strdup(userparts[1]);
+	g_strfreev(userparts);
+
+	/* Finally create our cancellable. */
+	connection->cancellable = g_cancellable_new();
+}
+
+static void
+purple_ircv3_connection_init(PurpleIRCv3Connection *connection) {
+}
+
+static void
+purple_ircv3_connection_class_finalize(PurpleIRCv3ConnectionClass *klass) {
+}
+
+static void
+purple_ircv3_connection_class_init(PurpleIRCv3ConnectionClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_ircv3_connection_get_property;
+	obj_class->set_property = purple_ircv3_connection_set_property;
+	obj_class->constructed = purple_ircv3_connection_constructed;
+	obj_class->dispose = purple_ircv3_connection_dispose;
+	obj_class->finalize = purple_ircv3_connection_finalize;
+
+	/**
+	 * PurpleIRCv3Connection:account:
+	 *
+	 * The [class@Purple.Account] that this connection is for.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ACCOUNT] = g_param_spec_object(
+		"account", "account",
+		"The account for this connection",
+		PURPLE_TYPE_ACCOUNT,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleIRCv3Connection:cancellable:
+	 *
+	 * The [class@Gio.Cancellable] for this connection.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_CANCELLABLE] = g_param_spec_object(
+		"cancellable", "cancellable",
+		"The cancellable for this connection",
+		G_TYPE_CANCELLABLE,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+void
+purple_ircv3_connection_register(GPluginNativePlugin *plugin) {
+	purple_ircv3_connection_register_type(G_TYPE_MODULE(plugin));
+}
+
+PurpleIRCv3Connection *
+purple_ircv3_connection_new(PurpleAccount *account) {
+	return g_object_new(
+		PURPLE_IRCV3_TYPE_CONNECTION,
+		"account", account,
+		NULL);
+}
+
+PurpleAccount *
+purple_ircv3_connection_get_account(PurpleIRCv3Connection *connection) {
+	g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL);
+
+	return connection->account;
+}
+
+GCancellable *
+purple_ircv3_connection_get_cancellable(PurpleIRCv3Connection *connection) {
+	g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL);
+
+	return connection->cancellable;
+}
+
+gboolean
+purple_ircv3_connection_valid(PurpleIRCv3Connection *connection,
+                              GError **error)
+{
+	g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
+
+	if(connection->validate_error != NULL) {
+		g_propagate_error(error, connection->validate_error);
+		connection->validate_error = NULL;
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+purple_ircv3_connection_connect(PurpleIRCv3Connection *connection) {
+	PurpleConnection *purple_connection = NULL;
+	GError *error = NULL;
+	GSocketClient *client = NULL;
+	gint default_port = PURPLE_IRCV3_DEFAULT_TLS_PORT;
+	gint port = 0;
+	gboolean use_tls = TRUE;
+
+	g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
+	g_return_if_fail(connection->connection == NULL);
+
+	purple_connection = purple_account_get_connection(connection->account);
+
+	client = purple_gio_socket_client_new(connection->account, &error);
+	if(!G_IS_SOCKET_CLIENT(client)) {
+		purple_connection_take_error(purple_connection, error);
+
+		return;
+	}
+
+	/* Turn on TLS if requested. */
+	use_tls = purple_account_get_bool(connection->account, "use-tls", TRUE);
+	g_socket_client_set_tls(client, use_tls);
+
+	/* If TLS is not being used, set the default port to the plain port. */
+	if(!use_tls) {
+		default_port = PURPLE_IRCV3_DEFAULT_PLAIN_PORT;
+	}
+	port = purple_account_get_int(connection->account, "port", default_port);
+
+	/* Finally start the async connection. */
+	g_socket_client_connect_to_host_async(client, connection->server_name,
+	                                      port, connection->cancellable,
+	                                      purple_ircv3_connection_connected_cb,
+	                                      purple_connection);
+
+	g_clear_object(&client);
+}
+
+void
+purple_ircv3_connection_close(PurpleIRCv3Connection *connection) {
+	g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
+
+	/* TODO: send QUIT command. */
+
+	/* Cancel the cancellable to tell everyone we're shutting down. */
+	if(G_IS_CANCELLABLE(connection->cancellable)) {
+		g_cancellable_cancel(connection->cancellable);
+
+		g_clear_object(&connection->cancellable);
+	}
+
+	if(G_IS_SOCKET_CONNECTION(connection->connection)) {
+		GInputStream *istream = G_INPUT_STREAM(connection->input);
+		GOutputStream *ostream = G_OUTPUT_STREAM(connection->output);
+
+		purple_gio_graceful_close(G_IO_STREAM(connection->connection),
+		                          istream, ostream);
+	}
+
+	g_clear_object(&connection->input);
+	g_clear_object(&connection->output);
+	g_clear_object(&connection->connection);
+}
diff --git a/libpurple/protocols/ircv3/purpleircv3core.h b/libpurple/protocols/ircv3/purpleircv3core.h
--- a/libpurple/protocols/ircv3/purpleircv3core.h
+++ b/libpurple/protocols/ircv3/purpleircv3core.h
@@ -21,6 +21,12 @@
 
 #include <glib.h>
 
+#define PURPLE_IRCV3_DEFAULT_SERVER "irc.libera.chat"
+#define PURPLE_IRCV3_DEFAULT_PLAIN_PORT 6667
+#define PURPLE_IRCV3_DEFAULT_TLS_PORT 6697
+
 #define PURPLE_IRCV3_DOMAIN (g_quark_from_static_string("ircv3-plugin"))
 
+#define PURPLE_IRCV3_CONNECTION_KEY "ircv3-connection"
+
 #endif /* PURPLE_IRCV3_CORE_H */
diff --git a/libpurple/protocols/ircv3/purpleircv3core.c b/libpurple/protocols/ircv3/purpleircv3core.c
--- a/libpurple/protocols/ircv3/purpleircv3core.c
+++ b/libpurple/protocols/ircv3/purpleircv3core.c
@@ -26,6 +26,7 @@
 
 #include "purpleircv3core.h"
 
+#include "purpleircv3connection.h"
 #include "purpleircv3protocol.h"
 
 /******************************************************************************
@@ -71,6 +72,7 @@
 	}
 
 	purple_ircv3_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin));
+	purple_ircv3_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin));
 
 	manager = purple_protocol_manager_get_default();
 
diff --git a/libpurple/protocols/ircv3/purpleircv3protocol.c b/libpurple/protocols/ircv3/purpleircv3protocol.c
--- a/libpurple/protocols/ircv3/purpleircv3protocol.c
+++ b/libpurple/protocols/ircv3/purpleircv3protocol.c
@@ -20,8 +20,8 @@
 
 #include "purpleircv3protocol.h"
 
-#define IRCV3_DEFAULT_SERVER ("irc.libera.chat")
-#define IRCV3_DEFAULT_PORT (6697)
+#include "purpleircv3connection.h"
+#include "purpleircv3core.h"
 
 typedef struct {
 	gboolean dummy;
@@ -35,7 +35,8 @@
 	PurpleAccountUserSplit *split = NULL;
 	GList *splits = NULL;
 
-	split = purple_account_user_split_new(_("Server"), IRCV3_DEFAULT_SERVER,
+	split = purple_account_user_split_new(_("Server"),
+	                                      PURPLE_IRCV3_DEFAULT_SERVER,
 	                                      '@');
 	splits = g_list_append(splits, split);
 
@@ -49,7 +50,7 @@
 	GList *options = NULL;
 
 	option = purple_account_option_int_new(_("Port"), "port",
-	                                       IRCV3_DEFAULT_PORT);
+	                                       PURPLE_IRCV3_DEFAULT_TLS_PORT);
 	options = g_list_append(options, option);
 
 	option = purple_account_option_bool_new(_("Use TLS"), "use-tls", TRUE);
@@ -83,6 +84,68 @@
 	return options;
 }
 
+static void
+purple_ircv3_protocol_login(G_GNUC_UNUSED PurpleProtocol *protocol,
+                            PurpleAccount *account)
+{
+	PurpleIRCv3Connection *connection = NULL;
+	PurpleConnection *purple_connection = NULL;
+	GError *error = NULL;
+
+	purple_connection = purple_account_get_connection(account);
+
+	connection = purple_ircv3_connection_new(account);
+	if(!purple_ircv3_connection_valid(connection, &error)) {
+		purple_connection_take_error(purple_connection, error);
+
+		return;
+	}
+
+	g_object_set_data_full(G_OBJECT(purple_connection),
+	                       PURPLE_IRCV3_CONNECTION_KEY,
+	                       connection, g_object_unref);
+
+	purple_ircv3_connection_connect(connection);
+}
+
+static void
+purple_ircv3_protocol_close(G_GNUC_UNUSED PurpleProtocol *protocol,
+                            PurpleConnection *purple_connection)
+{
+	PurpleIRCv3Connection *connection = NULL;
+
+	connection = g_object_get_data(G_OBJECT(purple_connection),
+	                               PURPLE_IRCV3_CONNECTION_KEY);
+
+	purple_ircv3_connection_close(connection);
+
+	/* Set our connection data to NULL which will remove the last reference. */
+	g_object_set_data(G_OBJECT(purple_connection), PURPLE_IRCV3_CONNECTION_KEY,
+	                  NULL);
+}
+
+static GList *
+purple_ircv3_protocol_status_types(G_GNUC_UNUSED PurpleProtocol *protocol,
+                                   PurpleAccount *account)
+{
+	PurpleStatusType *type = NULL;
+	GList *types = NULL;
+
+	type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE);
+	types = g_list_append(types, type);
+
+	type = purple_status_type_new_with_attrs(
+		PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
+		"message", _("Message"), purple_value_new(G_TYPE_STRING),
+		NULL);
+	types = g_list_append(types, type);
+
+	type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE);
+	types = g_list_append(types, type);
+
+	return types;
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -105,6 +168,9 @@
 	protocol_class->get_user_splits = purple_ircv3_protocol_get_user_splits;
 	protocol_class->get_account_options =
 		purple_ircv3_protocol_get_account_options;
+	protocol_class->login = purple_ircv3_protocol_login;
+	protocol_class->close = purple_ircv3_protocol_close;
+	protocol_class->status_types = purple_ircv3_protocol_status_types;
 }
 
 /******************************************************************************
