glib_salut_ft/gnio-sender.c

Source File

#include <unistd.h>
#include <string.h>

#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>

#include <telepathy-glib/telepathy-glib.h>

static GMainLoop *loop = NULL;
static TpDBusDaemon *bus_daemon = NULL;
static TpConnection *conn = NULL;

struct ft_state
{
	TpSocketAddressType type;
	GSocketConnection *connection;
	GSocketAddress *address;

	GFile *file;
	GInputStream *input;
	guint64 offset;
};

static void
handle_error (const GError *error)
{
	if (error)
	{
		g_print ("ERROR: %s\n", error->message);
		tp_cli_connection_call_disconnect (conn, -1, NULL,
				NULL, NULL, NULL);
	}
}

static void
provide_file_cb (TpChannel	*channel,
                 const GValue	*addressv,
		 const GError	*in_error,
		 gpointer	 user_data,
		 GObject	*weak_obj)
{
	struct ft_state *ftstate = (struct ft_state *) user_data;
	GError *error = NULL;

	handle_error (in_error);

	ftstate->address = tp_g_socket_address_from_variant (ftstate->type,
			addressv, NULL);
}

static void
splice_done_cb (GObject		*output,
		GAsyncResult	*res,
		gpointer	 user_data)
{
	struct ft_state *ftstate = (struct ft_state *) user_data;
	GError *error = NULL;

	g_output_stream_splice_finish (G_OUTPUT_STREAM (output), res, &error);
	handle_error (error);

	/* close the socket */
	g_io_stream_close (G_IO_STREAM (ftstate->connection),
			NULL, &error);
	handle_error (error);
}

static void
file_transfer_state_changed_cb (TpChannel	*channel,
                                guint		 state,
				guint		 reason,
				gpointer	 user_data,
				GObject		*weak_obj)
{
	struct ft_state *ftstate = (struct ft_state *) user_data;
	GError *error = NULL;

	g_print (" :: file_transfer_state_changed_cb (%i)\n", state);

	if (state == TP_FILE_TRANSFER_STATE_OPEN)
	{
		/* Begin Example 9-5 */
		GSocketClient *client = g_socket_client_new ();
		ftstate->connection = g_socket_client_connect (
				client,
				G_SOCKET_CONNECTABLE (ftstate->address),
				NULL, &error);
		handle_error (error);
		g_object_unref (client);

		/* we can now use the stream like any other GIO stream.
		 * Open an output stream for writing to */
		GOutputStream *output = g_io_stream_get_output_stream (
				G_IO_STREAM (ftstate->connection));
		ftstate->input = G_INPUT_STREAM (
				g_file_read (ftstate->file, NULL, &error));
		handle_error (error);

		g_seekable_seek (G_SEEKABLE (ftstate->input),
				ftstate->offset, G_SEEK_SET, NULL,
				&error);
		handle_error (error);

		/* splice the input stream into the output stream and GIO
		 * takes care of the rest */
		g_output_stream_splice_async (output, ftstate->input,
				G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
				0, NULL,
				splice_done_cb, ftstate);
		/* End Example 9-5 */
	}
	else if (state == TP_FILE_TRANSFER_STATE_COMPLETED ||
		 state == TP_FILE_TRANSFER_STATE_CANCELLED)
	{
		/* free the resources */
		g_object_unref (ftstate->connection);
		g_object_unref (ftstate->address);
		g_object_unref (ftstate->input);
		g_object_unref (ftstate->file);
		g_slice_free (struct ft_state, ftstate);
		tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
		g_print ("Done\n");
	}
}

static void
initial_offset_defined_cb (TpChannel	*channel,
			   guint64	 offset,
			   gpointer	 user_data,
			   GObject	*weak_obj)
{
	struct ft_state *ftstate = (struct ft_state *) user_data;

	g_print (" > initial_offset_defined_cb (%llu)\n", offset);
	ftstate->offset = offset;
}

static void
file_transfer_channel_ready (TpChannel		*channel,
                             const GError	*in_error,
			     gpointer		 user_data)
{
	GFile *file = G_FILE (user_data);
	GError *error = NULL;

	handle_error (in_error);

	GHashTable *map = tp_channel_borrow_immutable_properties (channel);

	const char *filename = tp_asv_get_string (map,
			TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME);
	guint64 size = tp_asv_get_uint64 (map,
			TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, NULL);

	g_print ("New file transfer to %s -- `%s' (%llu bytes)\n",
			tp_channel_get_identifier (channel),
			filename, size);

	/* Begin Example 9-3 */
	/* File transfers in Telepathy work by opening a socket to the
	 * Connection Manager and streaming the file over that socket.
	 * Let's find out what manner of sockets are supported by this CM */
	GHashTable *sockets = tp_asv_get_boxed (map,
		TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_AVAILABLE_SOCKET_TYPES,
		TP_HASH_TYPE_SUPPORTED_SOCKET_MAP);

	struct ft_state *ftstate = g_slice_new (struct ft_state);
	ftstate->file = file;
	guint access_control;

	/* let's try for IPv4 */
	if (g_hash_table_lookup (sockets,
				GINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_IPV4)))
	{
		ftstate->type = TP_SOCKET_ADDRESS_TYPE_IPV4;
		access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
	}
	else if (g_hash_table_lookup (sockets,
				GINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX)))
	{
		ftstate->type = TP_SOCKET_ADDRESS_TYPE_UNIX;
		access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
	}

	tp_cli_channel_type_file_transfer_connect_to_initial_offset_defined (
			channel, initial_offset_defined_cb,
			ftstate, NULL, NULL, &error);
	handle_error (error);

	tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
			channel, file_transfer_state_changed_cb,
			ftstate, NULL, NULL, &error);
	handle_error (error);

	/* set up the socket for providing the file */
	GValue *value = tp_g_value_slice_new_static_string ("");
	tp_cli_channel_type_file_transfer_call_provide_file (channel,
			-1, ftstate->type, access_control,
			value, provide_file_cb,
			ftstate, NULL, NULL);
	tp_g_value_slice_free (value);
	/* End Example 9-3 */
}

static void
create_ft_channel_cb (TpConnection	*conn,
                      const char	*object_path,
		      GHashTable	*properties,
		      const GError	*in_error,
		      gpointer		 user_data,
		      GObject		*weak_obj)
{
	GFile *file = G_FILE (user_data);
	GError *error = NULL;
	handle_error (in_error);

	TpChannel *channel = tp_channel_new_from_properties (conn, object_path,
			properties, &error);
	handle_error (error);

	tp_channel_call_when_ready (channel, file_transfer_channel_ready, file);
}

static void
iterate_contacts (TpChannel	 *channel,
		  GArray	 *handles,
		  char		**argv)
{
	GError *error = NULL;

	GFile *file = g_file_new_for_commandline_arg (argv[3]);
	GFileInfo *info = g_file_query_info (file,
			"standard::*",
			G_FILE_QUERY_INFO_NONE,
			NULL, &error);
	handle_error (error);

	GHashTable *props = tp_asv_new (
		TP_PROP_CHANNEL_CHANNEL_TYPE,
		G_TYPE_STRING,
		TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,

		TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
		G_TYPE_UINT,
		TP_HANDLE_TYPE_CONTACT,

		TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME,
		G_TYPE_STRING,
		g_file_info_get_display_name (info),

		TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE,
		G_TYPE_STRING,
		g_file_info_get_content_type (info),

		TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE,
		G_TYPE_UINT64,
		g_file_info_get_size (info),

		NULL);

	int i;
	for (i = 0; i < handles->len; i++)
	{
		int handle = g_array_index (handles, int, i);
		/* FIXME: we should check that our client has the
		 * FT capability */

		tp_asv_set_uint32 (props, TP_PROP_CHANNEL_TARGET_HANDLE,
				handle);

		tp_cli_connection_interface_requests_call_create_channel (
				conn, -1, props,
				create_ft_channel_cb,
				g_object_ref (file), NULL, NULL);
	}

	g_hash_table_destroy (props);
	g_object_unref (info);
	g_object_unref (file);
}

static void
group_members_changed_cb (TpChannel	 *channel,
			  char		 *message,
			  GArray	 *added,
			  GArray	 *removed,
			  GArray	 *local_pending,
			  GArray	 *remote_pending,
			  guint		  actor,
			  guint		  reason,
			  char		**argv)
{
	g_print (" :: group_members_changed_cb\n");
	g_print ("   channel contains %i new members\n", added->len);

	iterate_contacts (channel, added, argv);
}

static void
contact_list_channel_ready (TpChannel		*channel,
                            const GError	*in_error,
                            gpointer		 user_data)
{
	char **argv = (char **) user_data;
	GError *error = NULL;

	handle_error (in_error);

	g_print (" > contact_list_channel_ready\n");

	g_signal_connect (channel, "group-members-changed",
			G_CALLBACK (group_members_changed_cb), argv);

	const TpIntSet *members = tp_channel_group_get_members (channel);
	GArray *handles = tp_intset_to_array (members);
	g_print ("   channel contains %i members\n", handles->len);

	iterate_contacts (channel, handles, argv);
	g_array_free (handles, TRUE);
}

static void
create_contact_list_channel_cb (TpConnection	*conn,
                                gboolean	 yours,
                                const char	*object_path,
				GHashTable	*properties,
				const GError	*in_error,
				gpointer	 user_data,
				GObject		*weak_obj)
{
	char **argv = (char **) user_data;
	GError *error = NULL;

	handle_error (in_error);

	TpChannel *channel = tp_channel_new_from_properties (conn, object_path,
			properties, &error);
	handle_error (error);

	tp_channel_call_when_ready (channel, contact_list_channel_ready, argv);
}

static void
conn_ready (TpConnection	*conn,
            const GError	*in_error,
	    gpointer		 user_data)
{
	char **argv = (char **) user_data;
	GError *error = NULL;

	g_print (" > conn_ready\n");

	handle_error (in_error);

	/* check if the Requests interface is available */
	if (tp_proxy_has_interface_by_id (conn,
		TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
	{
		/* we need to ensure a contact list */
		GHashTable *props = tp_asv_new (
			TP_PROP_CHANNEL_CHANNEL_TYPE,
			G_TYPE_STRING,
			TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,

			TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
			G_TYPE_UINT,
			TP_HANDLE_TYPE_LIST,

			TP_PROP_CHANNEL_TARGET_ID,
			G_TYPE_STRING,
			"subscribe",

			NULL);

		tp_cli_connection_interface_requests_call_ensure_channel (
				conn, -1, props,
				create_contact_list_channel_cb,
				argv, NULL, NULL);
		g_hash_table_destroy (props);
	}
}

static void
status_changed_cb (TpConnection	*conn,
                   guint	 status,
		   guint	 reason,
		   gpointer	 user_data,
		   GObject	*weak_object)
{
	if (status == TP_CONNECTION_STATUS_DISCONNECTED)
	{
		g_print ("Disconnected\n");
		g_main_loop_quit (loop);
	}
	else if (status == TP_CONNECTION_STATUS_CONNECTED)
	{
		g_print ("Connected\n");
	}
}

static void
request_connection_cb (TpConnectionManager	*cm,
                       const char		*bus_name,
		       const char		*object_path,
		       const GError		*in_error,
		       gpointer			 user_data,
		       GObject			*weak_object)
{
	char **argv = (char **) user_data;
	GError *error = NULL;

	if (in_error) g_error ("%s", in_error->message);

	conn = tp_connection_new (bus_daemon, bus_name, object_path, &error);
	if (error) g_error ("%s", error->message);

	tp_connection_call_when_ready (conn, conn_ready, argv);

	tp_cli_connection_connect_to_status_changed (conn, status_changed_cb,
			NULL, NULL, NULL, &error);
	handle_error (error);

	/* initiate the connection */
	tp_cli_connection_call_connect (conn, -1, NULL, NULL, NULL, NULL);
}

static void
cm_ready (TpConnectionManager	*cm,
	  const GError		*in_error,
	  gpointer		 user_data,
	  GObject		*weak_obj)
{
	char **argv = (char **) user_data;

	g_print (" > cm_ready\n");

	if (in_error) g_error ("%s", in_error->message);

	const TpConnectionManagerProtocol *prot = tp_connection_manager_get_protocol (cm, "local-xmpp");
	if (!prot) g_error ("Protocol is not supported");

	/* request a new connection */
	GHashTable *parameters = tp_asv_new (
			"first-name", G_TYPE_STRING, argv[1],
			"last-name", G_TYPE_STRING, argv[2],
			NULL);

	tp_cli_connection_manager_call_request_connection (cm, -1,
			"local-xmpp",
			parameters,
			request_connection_cb,
			argv, NULL, NULL);

	g_hash_table_destroy (parameters);
}

static void
interrupt_cb (int signal)
{
	g_print ("Interrupt\n");
	/* disconnect */
	tp_cli_connection_call_disconnect (conn, -1, NULL, NULL, NULL, NULL);
}

int
main (int argc, char **argv)
{
	GError *error = NULL;

	g_type_init ();

	if (argc != 4)
	{
		g_error ("Must provide first name, last name and filename");
	}

	/* create a main loop */
	loop = g_main_loop_new (NULL, FALSE);

	/* acquire a connection to the D-Bus daemon */
	bus_daemon = tp_dbus_daemon_dup (&error);
	if (bus_daemon == NULL)
	{
		g_error ("%s", error->message);
	}

	/* we want to request the salut CM */
	TpConnectionManager *cm = tp_connection_manager_new (bus_daemon,
			"salut", NULL, &error);
	if (error) g_error ("%s", error->message);

	tp_connection_manager_call_when_ready (cm, cm_ready,
			argv, NULL, NULL);

	/* set up a signal handler */
	struct sigaction sa = { 0 };
	sa.sa_handler = interrupt_cb;
	sigaction (SIGINT, &sa, NULL);

	g_main_loop_run (loop);

	g_object_unref (bus_daemon);

	return 0;
}