Async connecting *almost* works. For some reason the G_IO_OUT condition
[cascardo/gnio.git] / gnio / gtcpclient.c
1 /* GNIO - GLib Network Layer of GIO
2  *
3  * Copyright (C) 2008 Christian Kellner, Samuel Cormier-Iijima
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors: Christian Kellner <gicmo@gnome.org>
21  *          Samuel Cormier-Iijima <sciyoshi@gmail.com>
22  */
23
24 #include <config.h>
25 #include <glib.h>
26 #include <gio/gio.h>
27 #include <gnio/gnio.h>
28
29 #include <string.h>
30 #include <errno.h>
31
32 G_DEFINE_TYPE (GTcpClient, g_tcp_client, G_TYPE_OBJECT);
33
34 enum
35 {
36   PROP_0,
37   PROP_ADDRESS,
38   PROP_HOSTNAME,
39   PROP_PORT
40 };
41
42 struct _GTcpClientPrivate
43 {
44   GInetSocketAddress *address;
45   gchar              *hostname;
46   gushort             port;
47   GSocket            *socket;
48 };
49
50 static void
51 g_tcp_client_constructed (GObject *object)
52 {
53   GTcpClient *client = G_TCP_CLIENT (object);
54
55   if (client->priv->address)
56     {
57       // we've been constructed with an address, extract hostname+port
58       client->priv->hostname = g_inet_address_to_string (g_inet_socket_address_get_address (client->priv->address));
59       client->priv->port = g_inet_socket_address_get_port (client->priv->address);
60       return;
61     }
62 }
63
64 static void
65 g_tcp_client_get_property (GObject    *object,
66                            guint       prop_id,
67                            GValue     *value,
68                            GParamSpec *pspec)
69 {
70   GTcpClient *client = G_TCP_CLIENT (object);
71
72   switch (prop_id)
73     {
74       case PROP_ADDRESS:
75         g_value_set_object (value, client->priv->address);
76         break;
77
78       case PROP_HOSTNAME:
79         g_value_set_string (value, client->priv->hostname);
80         break;
81
82       case PROP_PORT:
83         g_value_set_uint (value, client->priv->port);
84         break;
85
86       default:
87         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
88     }
89 }
90
91 static void
92 g_tcp_client_set_property (GObject      *object,
93                            guint         prop_id,
94                            const GValue *value,
95                            GParamSpec   *pspec)
96 {
97   GTcpClient *client = G_TCP_CLIENT (object);
98
99   switch (prop_id)
100     {
101       case PROP_ADDRESS:
102         // sink the address' floating reference
103         client->priv->address = G_INET_SOCKET_ADDRESS (g_value_get_object (value));
104         if (client->priv->address)
105           g_object_ref_sink (client->priv->address);
106         break;
107
108       case PROP_HOSTNAME:
109         client->priv->hostname = g_value_dup_string (value);
110         break;
111
112       case PROP_PORT:
113         client->priv->port = g_value_get_uint (value);
114         break;
115
116       default:
117         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
118     }
119 }
120
121 static void
122 g_tcp_client_finalize (GObject *object)
123 {
124   GTcpClient *client = G_TCP_CLIENT (object);
125
126   g_object_unref (client->priv->address);
127
128   if (G_OBJECT_CLASS (g_tcp_client_parent_class)->finalize)
129     (*G_OBJECT_CLASS (g_tcp_client_parent_class)->finalize) (object);
130 }
131
132 static void
133 g_tcp_client_dispose (GObject *object)
134 {
135   GTcpClient *client = G_TCP_CLIENT (object);
136
137   g_free (client->priv->hostname);
138
139   if (G_OBJECT_CLASS (g_tcp_client_parent_class)->dispose)
140     (*G_OBJECT_CLASS (g_tcp_client_parent_class)->dispose) (object);
141 }
142
143 static void
144 g_tcp_client_class_init (GTcpClientClass *klass)
145 {
146   GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass);
147
148   g_type_class_add_private (klass, sizeof (GTcpClientPrivate));
149
150   gobject_class->finalize = g_tcp_client_finalize;
151   gobject_class->dispose = g_tcp_client_dispose;
152   gobject_class->constructed = g_tcp_client_constructed;
153   gobject_class->set_property = g_tcp_client_set_property;
154   gobject_class->get_property = g_tcp_client_get_property;
155
156   g_object_class_install_property (gobject_class, PROP_ADDRESS,
157                                    g_param_spec_object ("address",
158                                                         "address",
159                                                         "the remote address the socket will connect to",
160                                                         G_TYPE_INET_SOCKET_ADDRESS,
161                                                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
162
163   g_object_class_install_property (gobject_class, PROP_HOSTNAME,
164                                    g_param_spec_string ("hostname",
165                                                         "hostname",
166                                                         "the hostname of the remote address the socket will connect to",
167                                                         NULL,
168                                                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
169
170   g_object_class_install_property (gobject_class, PROP_PORT,
171                                    g_param_spec_uint ("port",
172                                                       "port",
173                                                       "the remote port the socket will connect to",
174                                                       0,
175                                                       G_MAXUSHORT,
176                                                       0,
177                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
178 }
179
180 static void
181 g_tcp_client_init (GTcpClient *client)
182 {
183   client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client, G_TYPE_TCP_CLIENT, GTcpClientPrivate);
184
185   client->priv->address = NULL;
186   client->priv->hostname = NULL;
187   client->priv->port = 0;
188   client->priv->socket = NULL;
189 }
190
191 GTcpClient *
192 g_tcp_client_new (const gchar *hostname,
193                   gushort      port)
194 {
195   return G_TCP_CLIENT (g_object_new (G_TYPE_TCP_CLIENT, "hostname", hostname, "port", port, NULL));
196 }
197
198 GTcpClient *
199 g_tcp_client_new_from_address (GInetSocketAddress *address)
200 {
201   return G_TCP_CLIENT (g_object_new (G_TYPE_TCP_CLIENT, "address", address, NULL));
202 }
203
204 GNetworkInputStream *
205 g_tcp_client_get_input_stream (GTcpClient *client)
206 {
207   if (!client->priv->socket)
208     return NULL;
209
210   return _g_network_input_stream_new (client->priv->socket);
211 }
212
213 GNetworkOutputStream *
214 g_tcp_client_get_output_stream (GTcpClient *client)
215 {
216   if (!client->priv->socket)
217     return NULL;
218
219   return _g_network_output_stream_new (client->priv->socket);
220 }
221
222 gboolean
223 g_tcp_client_connect (GTcpClient    *client,
224                       GCancellable  *cancellable,
225                       GError       **error)
226 {
227   GInetAddress *address;
228
229   g_return_val_if_fail (G_IS_TCP_CLIENT (client), FALSE);
230
231   if (!client->priv->address)
232     {
233       // we've been constructed with just hostname+port, resolve
234       GResolver *resolver = g_resolver_new ();
235
236       address = g_resolver_resolve (resolver, client->priv->hostname, cancellable, error);
237
238       if (!address)
239         return FALSE;
240
241       client->priv->address = g_inet_socket_address_new (address, client->priv->port);
242
243       g_object_unref (resolver);
244
245       g_object_ref_sink (client->priv->address);
246     }
247   else
248     {
249       address = g_inet_socket_address_get_address (client->priv->address);
250     }
251
252   if (G_IS_INET4_ADDRESS (address))
253     client->priv->socket = g_socket_new (G_SOCKET_DOMAIN_INET, G_SOCKET_TYPE_STREAM, NULL, error);
254   else if (G_IS_INET6_ADDRESS (address))
255     client->priv->socket = g_socket_new (G_SOCKET_DOMAIN_INET6, G_SOCKET_TYPE_STREAM, NULL, error);
256   else
257     {
258       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported address domain");
259       return FALSE;
260     }
261
262   if (!client->priv->socket)
263     return FALSE;
264
265   if (g_cancellable_set_error_if_cancelled (cancellable, error))
266     return FALSE;
267
268   if (!g_socket_connect (client->priv->socket, G_SOCKET_ADDRESS (client->priv->address), error))
269     return FALSE;
270
271   return TRUE;
272 }
273
274 typedef struct {
275   GAsyncReadyCallback  callback;
276   GCancellable        *cancellable;
277   gpointer             user_data;
278   GTcpClient          *client;
279 } ConnectData;
280
281 static gboolean
282 connect_callback (ConnectData *data,
283                   GIOCondition condition,
284                   gint fd)
285 {
286   GTcpClient *client;
287   GSimpleAsyncResult *result;
288   GError *error = NULL;
289
290   client = data->client;
291
292   if (condition & G_IO_OUT)
293     {
294       result = g_simple_async_result_new (G_OBJECT (client), data->callback, data->user_data, g_tcp_client_connect_async);
295     }
296   else
297     {
298       if (!g_socket_has_socket_error (client->priv->socket, &error))
299         g_warning ("got G_IO_ERR but socket does not have error");
300
301       result = g_simple_async_result_new_from_error (G_OBJECT (client), data->callback, data->user_data, error);
302     }
303
304   g_simple_async_result_complete (result);
305
306   g_object_unref (result);
307
308   return FALSE;
309 }
310
311 static void
312 resolve_callback (GObject      *source,
313                   GAsyncResult *result,
314                   gpointer      user_data)
315 {
316   ConnectData *data = (ConnectData *) user_data;
317   GInetAddress *address;
318   GSimpleAsyncResult *error_result;
319   GError *error = NULL;
320
321   address = g_resolver_resolve_finish (G_RESOLVER (source), result, &error);
322
323   g_object_unref (G_RESOLVER (source));
324
325   if (!address)
326     {
327       error_result = g_simple_async_result_new_from_error (G_OBJECT (data->client), data->callback, data->user_data, error);
328
329       g_simple_async_result_complete (error_result);
330
331       g_object_unref (error_result);
332     }
333   else
334     {
335       data->client->priv->address = g_inet_socket_address_new (address, data->client->priv->port);
336
337       g_object_ref_sink (data->client->priv->address);
338
339       // at this point, the address has been resolved, so connect_async again
340       g_tcp_client_connect_async (data->client, data->cancellable, data->callback, data->user_data);
341     }
342
343   g_free (data);
344 }
345
346 void
347 g_tcp_client_connect_async (GTcpClient          *client,
348                             GCancellable        *cancellable,
349                             GAsyncReadyCallback  callback,
350                             gpointer             user_data)
351 {
352   GInetAddress *address;
353   GSimpleAsyncResult *result;
354   GSource *source;
355   ConnectData *data;
356   GError *error = NULL;
357
358   g_return_if_fail (G_IS_TCP_CLIENT (client));
359
360   if (!client->priv->address)
361     {
362       // we've been constructed with just hostname+port, resolve
363       GResolver *resolver = g_resolver_new ();
364
365       data = g_new (ConnectData, 1);
366
367       data->client = client;
368       data->callback = callback;
369       data->cancellable = cancellable;
370       data->user_data = user_data;
371
372       g_resolver_resolve_async (resolver, client->priv->hostname, cancellable, resolve_callback, data);
373
374       return;
375     }
376
377   address = g_inet_socket_address_get_address (client->priv->address);
378
379   if (G_IS_INET4_ADDRESS (address))
380     client->priv->socket = g_socket_new (G_SOCKET_DOMAIN_INET, G_SOCKET_TYPE_STREAM, NULL, &error);
381   else if (G_IS_INET6_ADDRESS (address))
382     client->priv->socket = g_socket_new (G_SOCKET_DOMAIN_INET6, G_SOCKET_TYPE_STREAM, NULL, &error);
383   else
384     {
385       g_simple_async_report_error_in_idle (G_OBJECT (client), callback, user_data, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported address domain");
386       return;
387     }
388
389   if (!client->priv->socket)
390     {
391       g_simple_async_report_gerror_in_idle (G_OBJECT (client), callback, user_data, error);
392       return;
393     }
394
395   g_socket_set_blocking (client->priv->socket, FALSE);
396
397   if (!g_socket_connect (client->priv->socket, G_SOCKET_ADDRESS (client->priv->address), &error))
398     {
399       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PENDING))
400         {
401           // the connection is in progress
402           source = g_socket_create_source (client->priv->socket, G_IO_OUT | G_IO_ERR | G_IO_HUP, cancellable);
403
404           data = g_new (ConnectData, 1);
405
406           data->client = client;
407           data->callback = callback;
408           data->cancellable = cancellable;
409           data->user_data = user_data;
410
411           g_source_set_callback (source, (GSourceFunc) connect_callback, data, g_free);
412
413           g_source_attach (source, NULL);
414         }
415       else
416         {
417           g_simple_async_report_gerror_in_idle (G_OBJECT (client), callback, user_data, error);
418         }
419     }
420   else
421     {
422       // the connection is already completed
423       result = g_simple_async_result_new (G_OBJECT (client), callback, user_data, g_tcp_client_connect_async);
424
425       g_simple_async_result_complete_in_idle (result);
426
427       g_object_unref (result);
428     }
429 }
430
431 gboolean
432 g_tcp_client_connect_finish (GTcpClient    *client,
433                              GAsyncResult  *result,
434                              GError       **error)
435 {
436   GSimpleAsyncResult *simple;
437
438   g_return_val_if_fail (G_IS_TCP_CLIENT (client), FALSE);
439
440   simple = G_SIMPLE_ASYNC_RESULT (result);
441
442   if (g_simple_async_result_propagate_error (simple, error))
443     return FALSE;
444
445   g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_tcp_client_connect_async);
446
447   return TRUE;
448 }
449
450 void
451 g_tcp_client_close (GTcpClient *tcp_client)
452 {
453   g_return_if_fail (G_IS_TCP_CLIENT (tcp_client));
454 }