tizen 2.3.1 release
[external/gupnp.git] / libgupnp / gupnp-service.c
1 /*
2  * Copyright (C) 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:gupnp-service
24  * @short_description: Class for service implementations.
25  *
26  * #GUPnPService allows for handling incoming actions and state variable
27  * notification. #GUPnPService implements the #GUPnPServiceInfo interface.
28  */
29
30 #include <gobject/gvaluecollector.h>
31 #include <gmodule.h>
32 #include <libsoup/soup-date.h>
33 #include <uuid/uuid.h>
34 #include <string.h>
35 #include "gupnp-service.h"
36 #include "gupnp-root-device.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-marshal.h"
39 #include "gupnp-error.h"
40 #include "http-headers.h"
41 #include "gena-protocol.h"
42 #include "xml-util.h"
43 #include "gvalue-util.h"
44
45 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
46
47 G_DEFINE_TYPE (GUPnPService,
48                gupnp_service,
49                GUPNP_TYPE_SERVICE_INFO);
50
51 struct _GUPnPServicePrivate {
52         GUPnPRootDevice *root_device;
53
54         SoupSession     *session;
55
56         guint            notify_available_id;
57
58         GHashTable      *subscriptions;
59
60         GList           *state_variables;
61
62         GQueue          *notify_queue;
63
64         gboolean         notify_frozen;
65 };
66
67 enum {
68         PROP_0,
69         PROP_ROOT_DEVICE
70 };
71
72 enum {
73         ACTION_INVOKED,
74         QUERY_VARIABLE,
75         NOTIFY_FAILED,
76         LAST_SIGNAL
77 };
78
79 static guint signals[LAST_SIGNAL];
80
81 static char *
82 create_property_set (GQueue *queue);
83
84 static void
85 notify_subscriber   (gpointer key,
86                      gpointer value,
87                      gpointer user_data);
88
89 typedef struct {
90         GUPnPService *service;
91
92         GList        *callbacks;
93         char         *sid;
94
95         int           seq;
96
97         GSource      *timeout_src;
98
99         GList        *pending_messages; /* Pending SoupMessages from this
100                                            subscription */
101 } SubscriptionData;
102
103 static void
104 send_initial_state (SubscriptionData *data);
105
106 static SoupSession *
107 gupnp_service_get_session (GUPnPService *service)
108 {
109         if (! service->priv->session) {
110                 GUPnPContext *context =
111                   gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
112
113                 /* Create a dedicated session for this service to
114                  * ensure that notifications are sent in the proper
115                  * order. The session from GUPnPContext may use
116                  * multiple connections.
117                  */
118                 service->priv->session = soup_session_async_new_with_options
119                   (SOUP_SESSION_IDLE_TIMEOUT, 60,
120                    SOUP_SESSION_ASYNC_CONTEXT,
121                    gssdp_client_get_main_context (GSSDP_CLIENT (context)),
122                    SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
123                    NULL);
124
125                 if (g_getenv ("GUPNP_DEBUG")) {
126                         SoupLogger *logger;
127                         logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
128                         soup_session_add_feature (
129                                         service->priv->session,
130                                         SOUP_SESSION_FEATURE (logger));
131                 }
132         }
133
134         return service->priv->session;
135 }
136
137 static void
138 subscription_data_free (SubscriptionData *data)
139 {
140         SoupSession *session;
141
142         session = gupnp_service_get_session (data->service);
143
144         /* Cancel pending messages */
145         while (data->pending_messages) {
146                 SoupMessage *msg;
147
148                 msg = data->pending_messages->data;
149
150                 soup_session_cancel_message (session,
151                                              msg,
152                                              SOUP_STATUS_CANCELLED);
153
154                 data->pending_messages =
155                         g_list_delete_link (data->pending_messages,
156                                             data->pending_messages);
157         }
158        
159         /* Further cleanup */
160         while (data->callbacks) {
161                 g_free (data->callbacks->data);
162                 data->callbacks = g_list_delete_link (data->callbacks,
163                                                       data->callbacks);
164         }
165
166         g_free (data->sid);
167
168         if (data->timeout_src)
169                 g_source_destroy (data->timeout_src);
170
171         g_slice_free (SubscriptionData, data);
172 }
173
174 typedef struct {
175         char  *variable;
176         GValue value;
177 } NotifyData;
178
179 static void
180 notify_data_free (NotifyData *data)
181 {
182         g_free (data->variable);
183         g_value_unset (&data->value);
184
185         g_slice_free (NotifyData, data);
186 }
187
188 struct _GUPnPServiceAction {
189         volatile gint ref_count;
190
191         GUPnPContext *context;
192
193         char         *name;
194
195         SoupMessage  *msg;
196
197         GUPnPXMLDoc  *doc;
198         xmlNode      *node;
199
200         GString      *response_str;
201 };
202
203 GUPnPServiceAction *
204 gupnp_service_action_ref (GUPnPServiceAction *action)
205 {
206         g_return_val_if_fail (action, NULL);
207         g_return_val_if_fail (action->ref_count > 0, NULL);
208
209         g_atomic_int_inc (&action->ref_count);
210
211         return action;
212 }
213
214 void
215 gupnp_service_action_unref (GUPnPServiceAction *action)
216 {
217         g_return_if_fail (action);
218         g_return_if_fail (action->ref_count > 0);
219
220         if (g_atomic_int_dec_and_test (&action->ref_count)) {
221                 g_free (action->name);
222                 g_object_unref (action->msg);
223                 g_object_unref (action->context);
224                 g_object_unref (action->doc);
225
226                 g_slice_free (GUPnPServiceAction, action);
227         }
228 }
229
230 /**
231  * gupnp_service_action_get_type:
232  *
233  * Get the gtype for GUPnPServiceActon
234  *
235  * Return value: The gtype of GUPnPServiceAction
236  **/
237 GType
238 gupnp_service_action_get_type (void)
239 {
240         static GType our_type = 0;
241
242         if (our_type == 0)
243                 our_type = g_boxed_type_register_static
244                         ("GUPnPServiceAction",
245                          (GBoxedCopyFunc) gupnp_service_action_ref,
246                          (GBoxedFreeFunc) gupnp_service_action_unref);
247
248         return our_type;
249 }
250
251 static void
252 finalize_action (GUPnPServiceAction *action)
253 {
254         SoupServer *server;
255         char *response_body;
256
257         /* Embed action->response_str in a SOAP document */
258         g_string_prepend (action->response_str,
259                           "<?xml version=\"1.0\"?>"
260                           "<s:Envelope xmlns:s="
261                                 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
262                           "s:encodingStyle="
263                                 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
264                           "<s:Body>");
265
266         if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
267                 g_string_append (action->response_str, "</u:");
268                 g_string_append (action->response_str, action->name);
269                 g_string_append (action->response_str, "Response>");
270         }
271
272         g_string_append (action->response_str,
273                          "</s:Body>"
274                          "</s:Envelope>");
275
276         response_body = g_string_free (action->response_str, FALSE);
277
278         soup_message_set_response (action->msg,
279                                    "text/xml; charset=\"utf-8\"",
280                                    SOUP_MEMORY_TAKE,
281                                    response_body,
282                                    strlen (response_body));
283
284         /* Server header on response */
285         soup_message_headers_append
286                         (action->msg->response_headers,
287                          "Server",
288                          gssdp_client_get_server_id
289                                 (GSSDP_CLIENT (action->context)));
290
291         /* Tell soup server that response is now ready */
292         server = gupnp_context_get_server (action->context);
293         soup_server_unpause_message (server, action->msg);
294
295         /* Cleanup */
296         gupnp_service_action_unref (action);
297 }
298
299 /**
300  * gupnp_service_action_get_name:
301  * @action: A #GUPnPServiceAction
302  *
303  * Get the name of @action.
304  *
305  * Return value: The name of @action
306  **/
307 const char *
308 gupnp_service_action_get_name (GUPnPServiceAction *action)
309 {
310         g_return_val_if_fail (action != NULL, NULL);
311
312         return action->name;
313 }
314
315 /**
316  * gupnp_service_action_get_locales:
317  * @action: A #GUPnPServiceAction
318  *
319  * Get an ordered (preferred first) #GList of locales preferred by
320  * the client. Free list and elements after use.
321  *
322  * Return value: (element-type utf8) (transfer full): A #GList of #char*
323  * locale names.
324  **/
325 GList *
326 gupnp_service_action_get_locales (GUPnPServiceAction *action)
327 {
328         g_return_val_if_fail (action != NULL, NULL);
329
330         return http_request_get_accept_locales (action->msg);
331 }
332
333 /**
334  * gupnp_service_action_get:
335  * @action: A #GUPnPServiceAction
336  * @Varargs: tuples of argument name, argument type, and argument value
337  * location, terminated with %NULL.
338  *
339  * Retrieves the specified action arguments.
340  **/
341 void
342 gupnp_service_action_get (GUPnPServiceAction *action,
343                           ...)
344 {
345         va_list var_args;
346
347         g_return_if_fail (action != NULL);
348
349         va_start (var_args, action);
350         gupnp_service_action_get_valist (action, var_args);
351         va_end (var_args);
352 }
353
354 /**
355  * gupnp_service_action_get_valist:
356  * @action: A #GUPnPServiceAction
357  * @var_args: va_list of tuples of argument name, argument type, and argument
358  * value location.
359  *
360  * See gupnp_service_action_get(); this version takes a va_list for
361  * use by language bindings.
362  **/
363 void
364 gupnp_service_action_get_valist (GUPnPServiceAction *action,
365                                  va_list             var_args)
366 {
367         const char *arg_name;
368         GType arg_type;
369         GValue value = {0, };
370         char *copy_error;
371
372         g_return_if_fail (action != NULL);
373
374         copy_error = NULL;
375
376         arg_name = va_arg (var_args, const char *);
377         while (arg_name) {
378                 arg_type = va_arg (var_args, GType);
379                 g_value_init (&value, arg_type);
380
381                 gupnp_service_action_get_value (action, arg_name, &value);
382
383                 G_VALUE_LCOPY (&value, var_args, 0, &copy_error);
384
385                 g_value_unset (&value);
386
387                 if (copy_error) {
388                         g_warning ("Error lcopying value: %s\n", copy_error);
389
390                         g_free (copy_error);
391                 }
392
393                 arg_name = va_arg (var_args, const char *);
394         }
395 }
396
397 /**
398  * gupnp_service_action_get_values:
399  * @action: A #GUPnPServiceAction
400  * @arg_names: (element-type utf8) : A #GList of argument names as string
401  * @arg_types: (element-type GType): A #GList of argument types as #GType
402  *
403  * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
404  *
405  * Return value: (element-type GValue) (transfer full): The values as #GList of
406  * #GValue. #g_list_free the returned list and #g_value_unset and #g_slice_free
407  * each element.
408  **/
409 GList *
410 gupnp_service_action_get_values (GUPnPServiceAction *action,
411                                  GList              *arg_names,
412                                  GList              *arg_types)
413 {
414         GList *arg_values;
415         int i;
416
417         g_return_val_if_fail (action != NULL, NULL);
418
419         arg_values = NULL;
420
421         for (i = 0; i < g_list_length (arg_names); i++) {
422                 const char *arg_name;
423                 GType arg_type;
424                 GValue *arg_value;
425
426                 arg_name = (const char *) g_list_nth_data (arg_names, i);
427                 arg_type = (GType) g_list_nth_data (arg_types, i);
428
429                 arg_value = g_slice_new0 (GValue);
430                 g_value_init (arg_value, arg_type);
431
432                 gupnp_service_action_get_value (action, arg_name, arg_value);
433
434                 arg_values = g_list_append (arg_values, arg_value);
435         }
436
437         return arg_values;
438 }
439
440 /**
441  * gupnp_service_action_get_value: (skip)
442  * @action: A #GUPnPServiceAction
443  * @argument: The name of the argument to retrieve
444  * @value: The #GValue to store the value of the argument, initialized
445  * to the correct type.
446  *
447  * Retrieves the value of @argument into @value.
448  **/
449 void
450 gupnp_service_action_get_value (GUPnPServiceAction *action,
451                                 const char         *argument,
452                                 GValue             *value)
453 {
454         xmlNode *node;
455         gboolean found;
456
457         g_return_if_fail (action != NULL);
458         g_return_if_fail (argument != NULL);
459         g_return_if_fail (value != NULL);
460
461         found = FALSE;
462         for (node = action->node->children; node; node = node->next) {
463                 if (strcmp ((char *) node->name, argument) != 0)
464                         continue;
465
466                 found = gvalue_util_set_value_from_xml_node (value, node);
467
468                 break;
469         }
470
471         if (!found)
472                 g_warning ("Failed to retreive '%s' argument of '%s' action",
473                            argument,
474                            action->name);
475 }
476
477 /**
478  * gupnp_service_action_get_gvalue:
479  * @action: A #GUPnPServiceAction
480  * @argument: The name of the argument to retrieve
481  * @type: The type of argument to retrieve
482  *
483  * Rename To: gupnp_service_action_get_value
484  * Retrieves the value of @argument into a GValue of type @type and returns it.
485  * The method exists only and only to satify PyGI, please use
486  * #gupnp_service_action_get_value and ignore this if possible.
487  *
488  * Return value: (transfer full): Value as #GValue associated with @action.
489  * #g_value_unset and #g_slice_free it after usage.
490  **/
491 GValue *
492 gupnp_service_action_get_gvalue (GUPnPServiceAction *action,
493                                        const char         *argument,
494                                        GType               type)
495 {
496         GValue *val;
497
498         val = g_slice_new0 (GValue);
499         g_value_init (val, type);
500
501         gupnp_service_action_get_value (action, argument, val);
502
503         return val;
504 }
505
506 /**
507  * gupnp_service_action_set:
508  * @action: A #GUPnPServiceAction
509  * @Varargs: tuples of return value name, return value type, and
510  * actual return value, terminated with %NULL.
511  *
512  * Sets the specified action return values.
513  **/
514 void
515 gupnp_service_action_set (GUPnPServiceAction *action,
516                           ...)
517 {
518         va_list var_args;
519
520         g_return_if_fail (action != NULL);
521
522         va_start (var_args, action);
523         gupnp_service_action_set_valist (action, var_args);
524         va_end (var_args);
525 }
526
527 /**
528  * gupnp_service_action_set_valist:
529  * @action: A #GUPnPServiceAction
530  * @var_args: va_list of tuples of return value name, return value type, and
531  * actual return value.
532  *
533  * See gupnp_service_action_set(); this version takes a va_list for
534  * use by language bindings.
535  **/
536 void
537 gupnp_service_action_set_valist (GUPnPServiceAction *action,
538                                  va_list             var_args)
539 {
540         const char *arg_name;
541         GType arg_type;
542         GValue value = {0, };
543         char *collect_error;
544
545         g_return_if_fail (action != NULL);
546
547         collect_error = NULL;
548
549         arg_name = va_arg (var_args, const char *);
550         while (arg_name) {
551                 arg_type = va_arg (var_args, GType);
552                 g_value_init (&value, arg_type);
553
554                 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
555                                  &collect_error);
556                 if (!collect_error) {
557                         gupnp_service_action_set_value (action,
558                                                         arg_name, &value);
559
560                         g_value_unset (&value);
561
562                 } else {
563                         g_warning ("Error collecting value: %s\n",
564                                    collect_error);
565
566                         g_free (collect_error);
567                 }
568
569                 arg_name = va_arg (var_args, const char *);
570         }
571 }
572
573 /**
574  * gupnp_service_action_set_values:
575  * @action: A #GUPnPServiceAction
576  * @arg_names: (element-type utf8) (transfer-none): A #GList of argument names
577  * @arg_values: (element-type GValue) (transfer-none): The #GList of values (as
578  * #GValues) that line up with @arg_names.
579  *
580  * Sets the specified action return values.
581  **/
582 void
583 gupnp_service_action_set_values (GUPnPServiceAction *action,
584                                  GList              *arg_names,
585                                  GList              *arg_values)
586 {
587         g_return_if_fail (action != NULL);
588         g_return_if_fail (arg_names != NULL);
589         g_return_if_fail (arg_values != NULL);
590         g_return_if_fail (g_list_length (arg_names) ==
591                           g_list_length (arg_values));
592
593         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
594                 g_warning ("Calling gupnp_service_action_set_value() after "
595                            "having called gupnp_service_action_return_error() "
596                            "is not allowed.");
597
598                 return;
599         }
600
601         /* Append to response */
602         for (; arg_names; arg_names = arg_names->next) {
603                 const char *arg_name;
604                 GValue *value;
605
606                 arg_name = arg_names->data;
607                 value = arg_values->data;
608
609                 xml_util_start_element (action->response_str, arg_name);
610                 gvalue_util_value_append_to_xml_string (value,
611                                                         action->response_str);
612                 xml_util_end_element (action->response_str, arg_name);
613
614                 arg_values = arg_values->next;
615         }
616 }
617
618 /**
619  * gupnp_service_action_set_value:
620  * @action: A #GUPnPServiceAction
621  * @argument: The name of the return value to retrieve
622  * @value: The #GValue to store the return value
623  *
624  * Sets the value of @argument to @value.
625  **/
626 void
627 gupnp_service_action_set_value (GUPnPServiceAction *action,
628                                 const char         *argument,
629                                 const GValue       *value)
630 {
631         g_return_if_fail (action != NULL);
632         g_return_if_fail (argument != NULL);
633         g_return_if_fail (value != NULL);
634
635         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
636                 g_warning ("Calling gupnp_service_action_set_value() after "
637                            "having called gupnp_service_action_return_error() "
638                            "is not allowed.");
639
640                 return;
641         }
642
643         /* Append to response */
644         xml_util_start_element (action->response_str, argument);
645         gvalue_util_value_append_to_xml_string (value, action->response_str);
646         xml_util_end_element (action->response_str, argument);
647 }
648
649 /**
650  * gupnp_service_action_return:
651  * @action: A #GUPnPServiceAction
652  *
653  * Return succesfully.
654  **/
655 void
656 gupnp_service_action_return (GUPnPServiceAction *action)
657 {
658         g_return_if_fail (action != NULL);
659
660         soup_message_set_status (action->msg, SOUP_STATUS_OK);
661
662         finalize_action (action);
663 }
664
665 /**
666  * gupnp_service_action_return_error:
667  * @action: A #GUPnPServiceAction
668  * @error_code: The error code
669  * @error_description: The error description, or %NULL if @error_code is
670  * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
671  * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
672  * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
673  * provided automatically.
674  *
675  * Return @error_code.
676  **/
677 void
678 gupnp_service_action_return_error (GUPnPServiceAction *action,
679                                    guint               error_code,
680                                    const char         *error_description)
681 {
682         g_return_if_fail (action != NULL);
683
684         switch (error_code) {
685         case GUPNP_CONTROL_ERROR_INVALID_ACTION:
686                 if (error_description == NULL)
687                         error_description = "Invalid Action";
688
689                 break;
690         case GUPNP_CONTROL_ERROR_INVALID_ARGS:
691                 if (error_description == NULL)
692                         error_description = "Invalid Args";
693
694                 break;
695         case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
696                 if (error_description == NULL)
697                         error_description = "Out of Sync";
698
699                 break;
700         case GUPNP_CONTROL_ERROR_ACTION_FAILED:
701                 if (error_description == NULL)
702                         error_description = "Action Failed";
703
704                 break;
705         default:
706                 g_return_if_fail (error_description != NULL);
707                 break;
708         }
709
710         /* Replace response_str with a SOAP Fault */
711         g_string_erase (action->response_str, 0, -1);
712
713         xml_util_start_element (action->response_str, "s:Fault");
714
715         xml_util_start_element (action->response_str, "faultcode");
716         g_string_append (action->response_str, "s:Client");
717         xml_util_end_element (action->response_str, "faultcode");
718
719         xml_util_start_element (action->response_str, "faultstring");
720         g_string_append (action->response_str, "UPnPError");
721         xml_util_end_element (action->response_str, "faultstring");
722
723         xml_util_start_element (action->response_str, "detail");
724
725         xml_util_start_element (action->response_str,
726                                 "UPnPError "
727                                 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
728
729         xml_util_start_element (action->response_str, "errorCode");
730         g_string_append_printf (action->response_str, "%u", error_code);
731         xml_util_end_element (action->response_str, "errorCode");
732
733         xml_util_start_element (action->response_str, "errorDescription");
734         xml_util_add_content (action->response_str, error_description);
735         xml_util_end_element (action->response_str, "errorDescription");
736
737         xml_util_end_element (action->response_str, "UPnPError");
738         xml_util_end_element (action->response_str, "detail");
739
740         xml_util_end_element (action->response_str, "s:Fault");
741
742         soup_message_set_status (action->msg,
743                                  SOUP_STATUS_INTERNAL_SERVER_ERROR);
744
745         finalize_action (action);
746 }
747
748 /**
749  * gupnp_service_action_get_message:
750  * @action: A #GUPnPServiceAction
751  *
752  * Get the #SoupMessage associated with @action. Mainly intended for
753  * applications to be able to read HTTP headers received from clients.
754  *
755  * Return value: (transfer full): #SoupMessage associated with @action. Unref
756  * after using it.
757  **/
758 SoupMessage *
759 gupnp_service_action_get_message (GUPnPServiceAction *action)
760 {
761         return g_object_ref (action->msg);
762 }
763
764 static void
765 gupnp_service_init (GUPnPService *service)
766 {
767         service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
768                                                      GUPNP_TYPE_SERVICE,
769                                                      GUPnPServicePrivate);
770
771         service->priv->subscriptions =
772                 g_hash_table_new_full (g_str_hash,
773                                        g_str_equal,
774                                        NULL,
775                                        (GDestroyNotify) subscription_data_free);
776
777         service->priv->notify_queue = g_queue_new ();
778 }
779
780 /* Generate a new action response node for @action_name */
781 static GString *
782 new_action_response_str (const char   *action_name,
783                          const char   *service_type)
784 {
785         GString *str;
786
787         str = xml_util_new_string ();
788
789         g_string_append (str, "<u:");
790         g_string_append (str, action_name);
791         g_string_append (str, "Response xmlns:u=");
792
793         if (service_type != NULL) {
794                 g_string_append (str, service_type);
795                 g_string_append_c (str, '"');
796         } else {
797                 g_warning ("No serviceType defined. Control may not work "
798                            "correctly.");
799         }
800
801         g_string_append_c (str, '>');
802
803         return str;
804 }
805
806 /* Handle QueryStateVariable action */
807 static void
808 query_state_variable (GUPnPService       *service,
809                       GUPnPServiceAction *action)
810 {
811         xmlNode *node;
812
813         /* Iterate requested variables */
814         for (node = action->node->children; node; node = node->next) {
815                 xmlChar *var_name;
816                 GValue value = {0,};
817
818                 if (strcmp ((char *) node->name, "varName") != 0)
819                         continue;
820
821                 /* varName */
822                 var_name = xmlNodeGetContent (node);
823                 if (!var_name) {
824                         gupnp_service_action_return_error (action,
825                                                            402,
826                                                            "Invalid Args");
827
828                         return;
829                 }
830
831                 /* Query variable */
832                 g_signal_emit (service,
833                                signals[QUERY_VARIABLE],
834                                g_quark_from_string ((char *) var_name),
835                                (char *) var_name,
836                                &value);
837
838                 if (!G_IS_VALUE (&value)) {
839                         gupnp_service_action_return_error (action,
840                                                            402,
841                                                            "Invalid Args");
842
843                         xmlFree (var_name);
844
845                         return;
846                 }
847
848                 /* Add variable to response */
849                 gupnp_service_action_set_value (action,
850                                                 (char *) var_name,
851                                                 &value);
852
853                 /* Cleanup */
854                 g_value_unset (&value);
855
856                 xmlFree (var_name);
857         }
858
859         gupnp_service_action_return (action);
860 }
861
862 /* controlURL handler */
863 static void
864 control_server_handler (SoupServer        *server,
865                         SoupMessage       *msg, 
866                         const char        *server_path,
867                         GHashTable        *query,
868                         SoupClientContext *soup_client,
869                         gpointer           user_data)
870 {
871         GUPnPService *service;
872         GUPnPContext *context;
873         xmlDoc *doc;
874         xmlNode *action_node;
875         const char *soap_action;
876         char *action_name;
877         char *end;
878         GUPnPServiceAction *action;
879
880         service = GUPNP_SERVICE (user_data);
881
882         if (msg->method != SOUP_METHOD_POST) {
883                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
884
885                 return;
886         }
887
888         if (msg->request_body->length == 0) {
889                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
890
891                 return;
892         }
893
894         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
895
896         /* Get action name */
897         soap_action = soup_message_headers_get_one (msg->request_headers,
898                                                     "SOAPAction");
899         if (!soap_action) {
900                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
901                 return;
902         }
903
904         action_name = strchr (soap_action, '#');
905         if (!action_name) {
906                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
907
908                 return;
909         }
910
911         /* This memory is libsoup-owned so we can do this */
912         *action_name = '\0';
913         action_name += 1;
914
915         /* This memory is libsoup-owned so we can do this */
916         end = strrchr (action_name, '"');
917         if (end)
918                 *end = '\0';
919
920         /* Parse action_node */
921         doc = xmlRecoverMemory (msg->request_body->data,
922                                 msg->request_body->length);
923         if (doc == NULL) {
924                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
925
926                 return;
927         }
928
929         action_node = xml_util_get_element ((xmlNode *) doc,
930                                             "Envelope",
931                                             "Body",
932                                             action_name,
933                                             NULL);
934         if (!action_node) {
935                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
936
937                 return;
938         }
939
940         /* Create action structure */
941         action = g_slice_new (GUPnPServiceAction);
942
943         action->ref_count    = 1;
944         action->name         = g_strdup (action_name);
945         action->msg          = g_object_ref (msg);
946         action->doc          = gupnp_xml_doc_new(doc);
947         action->node         = action_node;
948         action->response_str = new_action_response_str (action_name,
949                                                         soap_action);
950         action->context      = g_object_ref (context);
951
952         /* Tell soup server that response is not ready yet */
953         soup_server_pause_message (server, msg);
954
955         /* QueryStateVariable? */
956         if (strcmp (action_name, "QueryStateVariable") == 0)
957                 query_state_variable (service, action);
958         else {
959                 GQuark action_name_quark;
960
961                 action_name_quark = g_quark_from_string (action_name);
962                 if (g_signal_has_handler_pending (service,
963                                                   signals[ACTION_INVOKED],
964                                                   action_name_quark,
965                                                   FALSE)) {
966                         /* Emit signal. Handler parses request and fills in
967                          * response. */
968                         g_signal_emit (service,
969                                        signals[ACTION_INVOKED],
970                                        action_name_quark,
971                                        action);
972                 } else {
973                         /* No handlers attached. */
974                         gupnp_service_action_return_error (action,
975                                                            401,
976                                                            "Invalid Action");
977                 }
978         }
979 }
980
981 /* Generates a standard (re)subscription response */
982 static void
983 subscription_response (GUPnPService *service,
984                        SoupMessage  *msg,
985                        const char   *sid,
986                        int           timeout)
987 {
988         GUPnPContext *context;
989         GSSDPClient *client;
990         char *tmp;
991
992         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
993         client = GSSDP_CLIENT (context);
994
995         /* Server header on response */
996         soup_message_headers_append (msg->response_headers,
997                                      "Server",
998                                      gssdp_client_get_server_id (client));
999
1000         /* SID header */
1001         soup_message_headers_append (msg->response_headers,
1002                                      "SID",
1003                                      sid);
1004
1005         /* Timeout header */
1006         if (timeout > 0)
1007                 tmp = g_strdup_printf ("Second-%d", timeout);
1008         else
1009                 tmp = g_strdup ("infinite");
1010
1011         soup_message_headers_append (msg->response_headers,
1012                                      "Timeout",
1013                                      tmp);
1014         g_free (tmp);
1015
1016         /* 200 OK */
1017         soup_message_set_status (msg, SOUP_STATUS_OK);
1018 }
1019
1020 /* Generates a new SID */
1021 static char *
1022 generate_sid (void)
1023 {
1024         uuid_t id;
1025         char out[39];
1026
1027         uuid_generate (id);
1028         uuid_unparse (id, out);
1029
1030         return g_strdup_printf ("uuid:%s", out);
1031 }
1032
1033 /* Subscription expired */
1034 static gboolean
1035 subscription_timeout (gpointer user_data)
1036 {
1037         SubscriptionData *data;
1038
1039         data = user_data;
1040
1041         g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1042
1043         return FALSE;
1044 }
1045
1046 static void
1047 send_initial_state (SubscriptionData *data)
1048 {
1049         GQueue *queue;
1050         char *mem;
1051         GList *l;
1052
1053         /* Send initial event message */
1054         queue = g_queue_new ();
1055
1056         for (l = data->service->priv->state_variables; l; l = l->next) {
1057                 NotifyData *ndata;
1058
1059                 ndata = g_slice_new0 (NotifyData);
1060
1061                 g_signal_emit (data->service,
1062                                signals[QUERY_VARIABLE],
1063                                g_quark_from_string (l->data),
1064                                l->data,
1065                                &ndata->value);
1066
1067                 if (!G_IS_VALUE (&ndata->value)) {
1068                         g_slice_free (NotifyData, ndata);
1069
1070                         continue;
1071                 }
1072
1073                 ndata->variable = g_strdup (l->data);
1074
1075                 g_queue_push_tail (queue, ndata);
1076         }
1077
1078         mem = create_property_set (queue);
1079         notify_subscriber (data->sid, data, mem);
1080
1081         /* Cleanup */
1082         g_queue_free (queue);
1083
1084         g_free (mem);
1085 }
1086
1087
1088 /* Subscription request */
1089 static void
1090 subscribe (GUPnPService *service,
1091            SoupMessage  *msg,
1092            const char   *callback)
1093 {
1094         SubscriptionData *data;
1095         char *start, *end, *uri;
1096         GUPnPContext *context;
1097         GMainContext *main_context;
1098
1099         data = g_slice_new0 (SubscriptionData);
1100
1101         /* Parse callback list */
1102         start = (char *) callback;
1103         while ((start = strchr (start, '<'))) {
1104                 start += 1;
1105                 if (!start || !*start)
1106                         break;
1107
1108                 end = strchr (start, '>');
1109                 if (!end || !*end)
1110                         break;
1111
1112                 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1113                         uri = g_strndup (start, end - start);
1114                         data->callbacks = g_list_append (data->callbacks, uri);
1115                 }
1116
1117                 start = end;
1118         }
1119
1120         if (!data->callbacks) {
1121                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1122
1123                 g_slice_free (SubscriptionData, data);
1124
1125                 return;
1126         }
1127
1128         /* Add service and SID */
1129         data->service = service;
1130         data->sid     = generate_sid ();
1131
1132         /* Add timeout */
1133         data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1134         g_source_set_callback (data->timeout_src,
1135                                subscription_timeout,
1136                                data,
1137                                NULL);
1138
1139         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1140         main_context = gssdp_client_get_main_context (GSSDP_CLIENT (context));
1141         g_source_attach (data->timeout_src, main_context);
1142
1143         g_source_unref (data->timeout_src);
1144
1145         /* Add to hash */
1146         g_hash_table_insert (service->priv->subscriptions,
1147                              data->sid,
1148                              data);
1149
1150         /* Respond */
1151         subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1152
1153         send_initial_state (data);
1154 }
1155
1156 /* Resubscription request */
1157 static void
1158 resubscribe (GUPnPService *service,
1159              SoupMessage  *msg,
1160              const char   *sid)
1161 {
1162         SubscriptionData *data;
1163         GUPnPContext *context;
1164         GMainContext *main_context;
1165
1166         data = g_hash_table_lookup (service->priv->subscriptions, sid);
1167         if (!data) {
1168                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1169
1170                 return;
1171         }
1172
1173         /* Update timeout */
1174         if (data->timeout_src) {
1175                 g_source_destroy (data->timeout_src);
1176                 data->timeout_src = NULL;
1177         }
1178
1179         data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1180         g_source_set_callback (data->timeout_src,
1181                                subscription_timeout,
1182                                data,
1183                                NULL);
1184
1185         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1186         main_context = gssdp_client_get_main_context (GSSDP_CLIENT (context));
1187         g_source_attach (data->timeout_src, main_context);
1188
1189         g_source_unref (data->timeout_src);
1190
1191         /* Respond */
1192         subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1193 }
1194
1195 /* Unsubscription request */
1196 static void
1197 unsubscribe (GUPnPService *service,
1198              SoupMessage  *msg,
1199              const char   *sid)
1200 {
1201         if (g_hash_table_remove (service->priv->subscriptions, sid))
1202                 soup_message_set_status (msg, SOUP_STATUS_OK);
1203         else
1204                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1205 }
1206
1207 /* eventSubscriptionURL handler */
1208 static void
1209 subscription_server_handler (SoupServer        *server,
1210                              SoupMessage       *msg, 
1211                              const char        *server_path,
1212                              GHashTable        *query,
1213                              SoupClientContext *soup_client,
1214                              gpointer           user_data)
1215 {
1216         GUPnPService *service;
1217         const char *callback, *nt, *sid;
1218
1219         service = GUPNP_SERVICE (user_data);
1220
1221         callback = soup_message_headers_get_one (msg->request_headers,
1222                                                  "Callback");
1223         nt       = soup_message_headers_get_one (msg->request_headers, "NT");
1224         sid      = soup_message_headers_get_one (msg->request_headers, "SID");
1225
1226         /* Choose appropriate handler */
1227         if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1228                 if (callback) {
1229                         if (sid) {
1230                                 soup_message_set_status
1231                                         (msg, SOUP_STATUS_BAD_REQUEST);
1232
1233                         } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1234                                 soup_message_set_status
1235                                         (msg, SOUP_STATUS_PRECONDITION_FAILED);
1236
1237                         } else {
1238                                 subscribe (service, msg, callback);
1239
1240                         }
1241
1242                 } else if (sid) {
1243                         if (nt) {
1244                                 soup_message_set_status
1245                                         (msg, SOUP_STATUS_BAD_REQUEST);
1246
1247                         } else {
1248                                 resubscribe (service, msg, sid);
1249
1250                         }
1251
1252                 } else {
1253                         soup_message_set_status
1254                                 (msg, SOUP_STATUS_BAD_REQUEST);
1255
1256                 }
1257
1258         } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1259                 if (sid) {
1260                         if (nt || callback) {
1261                                 soup_message_set_status
1262                                         (msg, SOUP_STATUS_BAD_REQUEST);
1263
1264                         } else {
1265                                 unsubscribe (service, msg, sid);
1266
1267                         }
1268
1269                 } else {
1270                         soup_message_set_status
1271                                 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1272
1273                 }
1274
1275         } else {
1276                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1277
1278         }
1279 }
1280
1281 static void
1282 got_introspection (GUPnPServiceInfo          *info,
1283                    GUPnPServiceIntrospection *introspection,
1284                    const GError              *error,
1285                    gpointer                   user_data)
1286 {
1287         GUPnPService *service;
1288         const GList *state_variables, *l;
1289         GHashTableIter iter;
1290         gpointer data;
1291
1292         service = GUPNP_SERVICE (user_data);
1293
1294         if (introspection) {
1295                 state_variables =
1296                         gupnp_service_introspection_list_state_variables
1297                                 (introspection);
1298
1299                 for (l = state_variables; l; l = l->next) {
1300                         GUPnPServiceStateVariableInfo *variable;
1301
1302                         variable = l->data;
1303
1304                         if (!variable->send_events)
1305                                 continue;
1306
1307                         service->priv->state_variables =
1308                                 g_list_prepend (service->priv->state_variables,
1309                                                 g_strdup (variable->name));
1310                 }
1311
1312                 g_object_unref (introspection);
1313         } else
1314                 g_warning ("Failed to get SCPD: %s\n"
1315                            "The initial event message will not be sent.",
1316                            error ? error->message : "No error");
1317
1318         g_hash_table_iter_init (&iter, service->priv->subscriptions);
1319
1320         while (g_hash_table_iter_next (&iter, NULL, &data))
1321                 send_initial_state ((SubscriptionData *) data);
1322
1323         g_object_unref (service);
1324 }
1325
1326 static char *
1327 path_from_url (const char *url)
1328 {
1329         SoupURI *uri;
1330         gchar   *path;
1331
1332         uri = soup_uri_new (url);
1333         path = soup_uri_to_string (uri, TRUE);
1334         soup_uri_free (uri);
1335
1336         return path;
1337 }
1338
1339 static GObject *
1340 gupnp_service_constructor (GType                  type,
1341                            guint                  n_construct_params,
1342                            GObjectConstructParam *construct_params)
1343 {
1344         GObjectClass *object_class;
1345         GObject *object;
1346         GUPnPServiceInfo *info;
1347         GUPnPContext *context;
1348         SoupServer *server;
1349         char *url;
1350         char *path;
1351
1352         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1353
1354         /* Construct */
1355         object = object_class->constructor (type,
1356                                             n_construct_params,
1357                                             construct_params);
1358
1359         info    = GUPNP_SERVICE_INFO (object);
1360
1361         /* Get introspection and save state variable names */
1362         gupnp_service_info_get_introspection_async (info,
1363                                                     got_introspection,
1364                                                     object);
1365         g_object_ref (object);
1366
1367         /* Get server */
1368         context = gupnp_service_info_get_context (info);
1369         server = gupnp_context_get_server (context);
1370
1371         /* Run listener on controlURL */
1372         url = gupnp_service_info_get_control_url (info);
1373         path = path_from_url (url);
1374         soup_server_add_handler (server, path,
1375                                  control_server_handler, object, NULL);
1376         g_free (path);
1377         g_free (url);
1378
1379         /* Run listener on eventSubscriptionURL */
1380         url = gupnp_service_info_get_event_subscription_url (info);
1381         path = path_from_url (url);
1382         soup_server_add_handler (server, path,
1383                                  subscription_server_handler, object, NULL);
1384         g_free (path);
1385         g_free (url);
1386
1387         return object;
1388 }
1389
1390 /* Root device availability changed. */
1391 static void
1392 notify_available_cb (GObject *object,
1393                      GParamSpec *pspec,
1394                      gpointer    user_data)
1395 {
1396         GUPnPService *service;
1397
1398         service = GUPNP_SERVICE (user_data);
1399
1400         if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1401                 /* Root device now unavailable: Purge subscriptions */
1402                 g_hash_table_remove_all (service->priv->subscriptions);
1403         }
1404 }
1405
1406 static void
1407 gupnp_service_set_property (GObject      *object,
1408                             guint         property_id,
1409                             const GValue *value,
1410                             GParamSpec   *pspec)
1411 {
1412         GUPnPService *service;
1413
1414         service = GUPNP_SERVICE (object);
1415
1416         switch (property_id) {
1417         case PROP_ROOT_DEVICE: {
1418                 GUPnPRootDevice **dev;
1419
1420                 service->priv->root_device = g_value_get_object (value);
1421                 dev = &(service->priv->root_device);
1422
1423                 g_object_add_weak_pointer
1424                         (G_OBJECT (service->priv->root_device),
1425                          (gpointer *) dev);
1426
1427                 service->priv->notify_available_id =
1428                         g_signal_connect_object (service->priv->root_device,
1429                                                  "notify::available",
1430                                                  G_CALLBACK
1431                                                         (notify_available_cb),
1432                                                  object,
1433                                                  0);
1434
1435                 break;
1436         }
1437         default:
1438                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1439                 break;
1440         }
1441 }
1442
1443 static void
1444 gupnp_service_get_property (GObject    *object,
1445                             guint       property_id,
1446                             GValue     *value,
1447                             GParamSpec *pspec)
1448 {
1449         GUPnPService *service;
1450
1451         service = GUPNP_SERVICE (object);
1452
1453         switch (property_id) {
1454         case PROP_ROOT_DEVICE:
1455                 g_value_set_object (value, service->priv->root_device);
1456                 break;
1457         default:
1458                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1459                 break;
1460         }
1461 }
1462
1463 static void
1464 gupnp_service_dispose (GObject *object)
1465 {
1466         GUPnPService *service;
1467         GObjectClass *object_class;
1468         GUPnPServiceInfo *info;
1469         GUPnPContext *context;
1470         SoupServer *server;
1471         char *url;
1472         char *path;
1473
1474         service = GUPNP_SERVICE (object);
1475
1476         /* Get server */
1477         info = GUPNP_SERVICE_INFO (service);
1478         context = gupnp_service_info_get_context (info);
1479         server = gupnp_context_get_server (context);
1480
1481         /* Remove listener on controlURL */
1482         url = gupnp_service_info_get_control_url (info);
1483         path = path_from_url (url);
1484         soup_server_remove_handler (server, path);
1485         g_free (path);
1486         g_free (url);
1487
1488         /* Remove listener on eventSubscriptionURL */
1489         url = gupnp_service_info_get_event_subscription_url (info);
1490         path = path_from_url (url);
1491         soup_server_remove_handler (server, path);
1492         g_free (path);
1493         g_free (url);
1494
1495         if (service->priv->root_device) {
1496                 GUPnPRootDevice **dev = &(service->priv->root_device);
1497
1498                 if (g_signal_handler_is_connected
1499                         (service->priv->root_device,
1500                          service->priv->notify_available_id)) {
1501                         g_signal_handler_disconnect
1502                                 (service->priv->root_device,
1503                                  service->priv->notify_available_id);
1504                 }
1505
1506                 g_object_remove_weak_pointer
1507                         (G_OBJECT (service->priv->root_device),
1508                          (gpointer *) dev);
1509
1510                 service->priv->root_device = NULL;
1511         }
1512
1513         /* Cancel pending messages */
1514         g_hash_table_remove_all (service->priv->subscriptions);
1515
1516         /* Call super */
1517         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1518         object_class->dispose (object);
1519 }
1520
1521 static void
1522 gupnp_service_finalize (GObject *object)
1523 {
1524         GUPnPService *service;
1525         GObjectClass *object_class;
1526         NotifyData *data;
1527
1528         service = GUPNP_SERVICE (object);
1529
1530         /* Free subscription hash */
1531         g_hash_table_destroy (service->priv->subscriptions);
1532
1533         /* Free state variable list */
1534         while (service->priv->state_variables) {
1535                 g_free (service->priv->state_variables->data);
1536                 service->priv->state_variables =
1537                         g_list_delete_link (service->priv->state_variables,
1538                                             service->priv->state_variables);
1539         }
1540
1541         /* Free notify queue */
1542         while ((data = g_queue_pop_head (service->priv->notify_queue)))
1543                 notify_data_free (data);
1544
1545         g_queue_free (service->priv->notify_queue);
1546
1547         if (service->priv->session) {
1548                 g_object_unref (service->priv->session);
1549                 service->priv->session = NULL;
1550         }
1551
1552         /* Call super */
1553         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1554         object_class->finalize (object);
1555 }
1556
1557 static void
1558 gupnp_service_class_init (GUPnPServiceClass *klass)
1559 {
1560         GObjectClass *object_class;
1561
1562         object_class = G_OBJECT_CLASS (klass);
1563
1564         object_class->set_property = gupnp_service_set_property;
1565         object_class->get_property = gupnp_service_get_property;
1566         object_class->constructor  = gupnp_service_constructor;
1567         object_class->dispose      = gupnp_service_dispose;
1568         object_class->finalize     = gupnp_service_finalize;
1569
1570         g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1571
1572         /**
1573          * GUPnPService:root-device:
1574          *
1575          * The containing #GUPnPRootDevice.
1576          **/
1577         g_object_class_install_property
1578                 (object_class,
1579                  PROP_ROOT_DEVICE,
1580                  g_param_spec_object ("root-device",
1581                                       "Root device",
1582                                       "The GUPnPRootDevice",
1583                                       GUPNP_TYPE_ROOT_DEVICE,
1584                                       G_PARAM_READWRITE |
1585                                       G_PARAM_CONSTRUCT_ONLY |
1586                                       G_PARAM_STATIC_NAME |
1587                                       G_PARAM_STATIC_NICK |
1588                                       G_PARAM_STATIC_BLURB));
1589
1590         /**
1591          * GUPnPService::action-invoked:
1592          * @service: The #GUPnPService that received the signal
1593          * @action: The invoked #GUPnPAction
1594          *
1595          * Emitted whenever an action is invoked. Handler should process
1596          * @action and must call either gupnp_service_action_return() or
1597          * gupnp_service_action_return_error().
1598          **/
1599         signals[ACTION_INVOKED] =
1600                 g_signal_new ("action-invoked",
1601                               GUPNP_TYPE_SERVICE,
1602                               G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1603                               G_STRUCT_OFFSET (GUPnPServiceClass,
1604                                                action_invoked),
1605                               NULL,
1606                               NULL,
1607                               g_cclosure_marshal_VOID__BOXED,
1608                               G_TYPE_NONE,
1609                               1,
1610                               GUPNP_TYPE_SERVICE_ACTION);
1611
1612         /**
1613          * GUPnPService::query-variable:
1614          * @service: The #GUPnPService that received the signal
1615          * @variable: The variable that is being queried
1616          * @value: The location of the #GValue of the variable
1617          *
1618          * Emitted whenever @service needs to know the value of @variable.
1619          * Handler should fill @value with the value of @variable.
1620          **/
1621         signals[QUERY_VARIABLE] =
1622                 g_signal_new ("query-variable",
1623                               GUPNP_TYPE_SERVICE,
1624                               G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1625                               G_STRUCT_OFFSET (GUPnPServiceClass,
1626                                                query_variable),
1627                               NULL,
1628                               NULL,
1629                               gupnp_marshal_VOID__STRING_POINTER,
1630                               G_TYPE_NONE,
1631                               2,
1632                               G_TYPE_STRING,
1633                               G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1634                                                 is an outward argument! */);
1635
1636         /**
1637          * GUPnPService::notify-failed:
1638          * @service: The #GUPnPService that received the signal
1639          * @callback_url: The callback URL
1640          * @reason: A pointer to a #GError describing why the notify failed
1641          *
1642          * Emitted whenever notification of a client fails.
1643          **/
1644         signals[NOTIFY_FAILED] =
1645                 g_signal_new ("notify-failed",
1646                               GUPNP_TYPE_SERVICE,
1647                               G_SIGNAL_RUN_LAST,
1648                               G_STRUCT_OFFSET (GUPnPServiceClass,
1649                                                notify_failed),
1650                               NULL,
1651                               NULL,
1652                               gupnp_marshal_VOID__POINTER_POINTER,
1653                               G_TYPE_NONE,
1654                               2,
1655                               G_TYPE_POINTER,
1656                               G_TYPE_POINTER);
1657 }
1658
1659 /**
1660  * gupnp_service_notify:
1661  * @service: A #GUPnPService
1662  * @Varargs: Tuples of variable name, variable type, and variable value,
1663  * terminated with %NULL.
1664  *
1665  * Notifies listening clients that the properties listed in @Varargs
1666  * have changed to the specified values.
1667  **/
1668 void
1669 gupnp_service_notify (GUPnPService *service,
1670                       ...)
1671 {
1672         va_list var_args;
1673
1674         g_return_if_fail (GUPNP_IS_SERVICE (service));
1675
1676         va_start (var_args, service);
1677         gupnp_service_notify_valist (service, var_args);
1678         va_end (var_args);
1679 }
1680
1681 /**
1682  * gupnp_service_notify_valist:
1683  * @service: A #GUPnPService
1684  * @var_args: A va_list of tuples of variable name, variable type, and variable
1685  * value, terminated with %NULL.
1686  *
1687  * See gupnp_service_notify(); this version takes a va_list for
1688  * use by language bindings.
1689  **/
1690 void
1691 gupnp_service_notify_valist (GUPnPService *service,
1692                              va_list       var_args)
1693 {
1694         const char *var_name;
1695         GType var_type;
1696         GValue value = {0, };
1697         char *collect_error;
1698
1699         g_return_if_fail (GUPNP_IS_SERVICE (service));
1700
1701         collect_error = NULL;
1702
1703         var_name = va_arg (var_args, const char *);
1704         while (var_name) {
1705                 var_type = va_arg (var_args, GType);
1706                 g_value_init (&value, var_type);
1707
1708                 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1709                                  &collect_error);
1710                 if (!collect_error) {
1711                         gupnp_service_notify_value (service, var_name, &value);
1712
1713                         g_value_unset (&value);
1714
1715                 } else {
1716                         g_warning ("Error collecting value: %s\n",
1717                                    collect_error);
1718
1719                         g_free (collect_error);
1720                 }
1721
1722                 var_name = va_arg (var_args, const char *);
1723         }
1724 }
1725
1726 /* Received notify response. */
1727 static void
1728 notify_got_response (SoupSession *session,
1729                      SoupMessage *msg,
1730                      gpointer     user_data)
1731 {
1732         SubscriptionData *data;
1733
1734         /* Cancelled? */
1735         if (msg->status_code == SOUP_STATUS_CANCELLED)
1736                 return;
1737
1738         data = user_data;
1739
1740         /* Remove from pending messages list */
1741         data->pending_messages = g_list_remove (data->pending_messages, msg);
1742
1743         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1744                 /* Success: reset callbacks pointer */
1745                 data->callbacks = g_list_first (data->callbacks);
1746
1747         } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1748                 /* Precondition failed: Cancel subscription */
1749                 g_hash_table_remove (data->service->priv->subscriptions,
1750                                      data->sid);
1751
1752         } else {
1753                 /* Other failure: Try next callback or signal failure. */
1754                 if (data->callbacks->next) {
1755                         SoupURI *uri;
1756                         SoupSession *service_session;
1757
1758                         /* Call next callback */
1759                         data->callbacks = data->callbacks->next;
1760
1761                         uri = soup_uri_new (data->callbacks->data);
1762                         soup_message_set_uri (msg, uri);
1763                         soup_uri_free (uri);
1764
1765                         /* And re-queue */
1766                         data->pending_messages = 
1767                                 g_list_prepend (data->pending_messages, msg);
1768
1769                         service_session = gupnp_service_get_session (data->service);
1770
1771                         soup_session_requeue_message (service_session, msg);
1772                 } else {
1773                         /* Emit 'notify-failed' signal */
1774                         GError *error;
1775
1776                         error = g_error_new_literal
1777                                         (GUPNP_EVENTING_ERROR,
1778                                          GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1779                                          msg->reason_phrase);
1780
1781                         g_signal_emit (data->service,
1782                                        signals[NOTIFY_FAILED],
1783                                        0,
1784                                        data->callbacks,
1785                                        error);
1786
1787                         g_error_free (error);
1788
1789                         /* Reset callbacks pointer */
1790                         data->callbacks = g_list_first (data->callbacks);
1791                 }
1792         }
1793 }
1794
1795 /* Send notification @user_data to subscriber @value */
1796 static void
1797 notify_subscriber (gpointer key,
1798                    gpointer value,
1799                    gpointer user_data)
1800 {
1801         SubscriptionData *data;
1802         const char *property_set;
1803         char *tmp;
1804         SoupMessage *msg;
1805         SoupSession *session;
1806
1807         data = value;
1808         property_set = user_data;
1809
1810         /* Create message */
1811         msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1812         if (!msg) {
1813                 g_warning ("Invalid callback URL: %s",
1814                            (char *) data->callbacks->data);
1815
1816                 return;
1817         }
1818
1819         soup_message_headers_append (msg->request_headers,
1820                                      "NT",
1821                                      "upnp:event");
1822
1823         soup_message_headers_append (msg->request_headers,
1824                                      "NTS",
1825                                      "upnp:propchange");
1826
1827         soup_message_headers_append (msg->request_headers,
1828                                      "SID",
1829                                      data->sid);
1830
1831         tmp = g_strdup_printf ("%d", data->seq);
1832         soup_message_headers_append (msg->request_headers,
1833                                      "SEQ",
1834                                      tmp);
1835         g_free (tmp);
1836
1837         /* Handle overflow */
1838         if (data->seq < G_MAXINT32)
1839                 data->seq++;
1840         else
1841                 data->seq = 1;
1842
1843         /* Add body */
1844         soup_message_set_request (msg,
1845                                   "text/xml; charset=utf-8",
1846                                   SOUP_MEMORY_TAKE,
1847                                   g_strdup (property_set),
1848                                   strlen (property_set));
1849
1850         /* Queue */
1851         data->pending_messages = g_list_prepend (data->pending_messages, msg);
1852
1853         session = gupnp_service_get_session (data->service);
1854
1855         soup_session_queue_message (session,
1856                                     msg,
1857                                     notify_got_response,
1858                                     data);
1859 }
1860
1861 /* Create a property set from @queue */
1862 static char *
1863 create_property_set (GQueue *queue)
1864 {
1865         NotifyData *data;
1866         GString *str;
1867
1868         /* Compose property set */
1869         str = xml_util_new_string ();
1870
1871         g_string_append (str,
1872                          "<?xml version=\"1.0\"?>"
1873                          "<e:propertyset xmlns:e="
1874                                 "\"urn:schemas-upnp-org:event-1-0\">");
1875
1876         /* Add variables */
1877         while ((data = g_queue_pop_head (queue))) {
1878                 xml_util_start_element (str, "e:property");
1879                 xml_util_start_element (str, data->variable);
1880                 gvalue_util_value_append_to_xml_string (&data->value, str);
1881                 xml_util_end_element (str, data->variable);
1882                 xml_util_end_element (str, "e:property");
1883
1884                 /* Cleanup */
1885                 notify_data_free (data);
1886         }
1887
1888         g_string_append (str, "</e:propertyset>");
1889
1890         /* Cleanup & return */
1891         return g_string_free (str, FALSE);
1892 }
1893
1894 /* Flush all queued notifications */
1895 static void
1896 flush_notifications (GUPnPService *service)
1897 {
1898         char *mem;
1899
1900         /* Create property set */
1901         mem = create_property_set (service->priv->notify_queue);
1902
1903         /* And send it off */
1904         g_hash_table_foreach (service->priv->subscriptions,
1905                               notify_subscriber,
1906                               mem);
1907
1908         /* Cleanup */
1909         g_free (mem);
1910 }
1911
1912 /**
1913  * gupnp_service_notify_value:
1914  * @service: A #GUPnPService
1915  * @variable: The name of the variable to notify
1916  * @value: The value of the variable
1917  *
1918  * Notifies listening clients that @variable has changed to @value.
1919  **/
1920 void
1921 gupnp_service_notify_value (GUPnPService *service,
1922                             const char   *variable,
1923                             const GValue *value)
1924 {
1925         NotifyData *data;
1926
1927         g_return_if_fail (GUPNP_IS_SERVICE (service));
1928         g_return_if_fail (variable != NULL);
1929         g_return_if_fail (G_IS_VALUE (value));
1930
1931         /* Queue */
1932         data = g_slice_new0 (NotifyData);
1933
1934         data->variable = g_strdup (variable);
1935
1936         g_value_init (&data->value, G_VALUE_TYPE (value));
1937         g_value_copy (value, &data->value);
1938
1939         g_queue_push_tail (service->priv->notify_queue, data);
1940
1941         /* And flush, if not frozen */
1942         if (!service->priv->notify_frozen)
1943                 flush_notifications (service);
1944 }
1945
1946 /**
1947  * gupnp_service_freeze_notify:
1948  * @service: A #GUPnPService
1949  *
1950  * Causes new notifications to be queued up until gupnp_service_thaw_notify()
1951  * is called.
1952  **/
1953 void
1954 gupnp_service_freeze_notify (GUPnPService *service)
1955 {
1956         g_return_if_fail (GUPNP_IS_SERVICE (service));
1957
1958         service->priv->notify_frozen = TRUE;
1959 }
1960
1961 /**
1962  * gupnp_service_thaw_notify:
1963  * @service: A #GUPnPService
1964  *
1965  * Sends out any pending notifications, and stops queuing of new ones.
1966  **/
1967 void
1968 gupnp_service_thaw_notify (GUPnPService *service)
1969 {
1970         g_return_if_fail (GUPNP_IS_SERVICE (service));
1971
1972         service->priv->notify_frozen = FALSE;
1973
1974         if (g_queue_get_length (service->priv->notify_queue) == 0)
1975                 return; /* Empty notify queue */
1976
1977         flush_notifications (service);
1978 }
1979
1980 /* Convert a CamelCase string to a lowercase string with underscores */
1981 static char *
1982 strip_camel_case (char *camel_str)
1983 {
1984         char *stripped;
1985         unsigned int i, j;
1986
1987         /* Keep enough space for underscores */
1988         stripped = g_malloc (strlen (camel_str) * 2);
1989
1990         for (i = 0, j = 0; i <= strlen (camel_str); i++) {
1991                 /* Convert every upper case letter to lower case and unless
1992                  * it's the first character, the last charachter, in the
1993                  * middle of an abbreviation or there is already an underscore
1994                  * before it, add an underscore before it */
1995                 if (g_ascii_isupper (camel_str[i])) {
1996                         if (i != 0 &&
1997                             camel_str[i + 1] != '\0' &&
1998                             camel_str[i - 1] != '_' &&
1999                             !g_ascii_isupper (camel_str[i - 1])) {
2000                                 stripped[j++] = '_';
2001                         }
2002                         stripped[j++] = g_ascii_tolower (camel_str[i]);
2003                 } else
2004                         stripped[j++] = camel_str[i];
2005         }
2006
2007         return stripped;
2008 }
2009
2010 static GCallback
2011 find_callback_by_name (GModule    *module,
2012                        const char *name)
2013 {
2014         GCallback callback;
2015         char *full_name;
2016
2017         /* First try with 'on_' prefix */
2018         full_name = g_strjoin ("_",
2019                                "on",
2020                                name,
2021                                NULL);
2022
2023         if (!g_module_symbol (module,
2024                               full_name,
2025                               (gpointer) &callback)) {
2026                 g_free (full_name);
2027
2028                 /* Now try with '_cb' postfix */
2029                 full_name = g_strjoin ("_",
2030                                        name,
2031                                        "cb",
2032                                        NULL);
2033
2034                 g_module_symbol (module,
2035                                  full_name,
2036                                  (gpointer) &callback);
2037         }
2038
2039         g_free (full_name);
2040
2041         return callback;
2042 }
2043
2044 /* Use the strings from @name_list as details to @signal_name, and connect
2045  * callbacks with names based on these same strings to @signal_name::string. */
2046 static void
2047 connect_names_to_signal_handlers (GUPnPService *service,
2048                                   GModule      *module,
2049                                   const GList  *name_list,
2050                                   const char   *signal_name,
2051                                   const char   *callback_prefix,
2052                                   gpointer      user_data)
2053 {
2054         const GList *name_node;
2055
2056         for (name_node = name_list;
2057              name_node;
2058              name_node = name_node->next) {
2059                 GCallback callback;
2060                 char     *callback_name;
2061                 char     *signal_detail;
2062
2063                 signal_detail = (char *) name_node->data;
2064                 callback_name = strip_camel_case (signal_detail);
2065
2066                 if (callback_prefix) {
2067                         char *tmp;
2068
2069                         tmp = g_strjoin ("_",
2070                                          callback_prefix,
2071                                          callback_name,
2072                                          NULL);
2073
2074                         g_free (callback_name);
2075                         callback_name = tmp;
2076                 }
2077
2078                 callback = find_callback_by_name (module, callback_name);
2079                 g_free (callback_name);
2080
2081                 if (callback == NULL)
2082                         continue;
2083
2084                 signal_detail = g_strjoin ("::",
2085                                            signal_name,
2086                                            signal_detail,
2087                                            NULL);
2088
2089                 g_signal_connect (service,
2090                                   signal_detail,
2091                                   callback,
2092                                   user_data);
2093
2094                 g_free (signal_detail);
2095         }
2096 }
2097
2098 /**
2099  * gupnp_service_signals_autoconnect:
2100  * @service: A #GUPnPService
2101  * @user_data: the data to pass to each of the callbacks
2102  * @error: return location for a #GError, or %NULL
2103  *
2104  * A convenience function that attempts to connect all possible
2105  * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2106  * appropriate callbacks for the service @service. It uses service introspection
2107  * and GModule's introspective features. It is very simillar to
2108  * glade_xml_signal_autoconnect() except that it attempts to guess the names of
2109  * the signal handlers on its own.
2110  *
2111  * For this function to do its magic, the application must name the callback
2112  * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2113  * off the action names and either prepend "on_" or append "_cb" to them. Same
2114  * goes for #GUPnPService::query-variable signals, except that "query_" should
2115  * be prepended to the variable name. For example, callback function for
2116  * "GetSystemUpdateID" action should be either named as
2117  * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2118  * for the query of "SystemUpdateID" state variable should be named
2119  * "query_system_update_id_cb" or "on_query_system_update_id".
2120  *
2121  * Note that this function will not work correctly if GModule is not supported
2122  * on the platform or introspection is not available for service @service.
2123  *
2124  * WARNING: This function can not and therefore does not guarantee that the
2125  * resulting signal connections will be correct as it depends heavily on a
2126  * particular naming schemes described above.
2127  **/
2128 void
2129 gupnp_service_signals_autoconnect (GUPnPService *service,
2130                                    gpointer      user_data,
2131                                    GError      **error)
2132 {
2133         GUPnPServiceIntrospection *introspection;
2134         const GList               *names;
2135         GModule                   *module;
2136
2137         g_return_if_fail (GUPNP_IS_SERVICE (service));
2138
2139         introspection = gupnp_service_info_get_introspection
2140                                 (GUPNP_SERVICE_INFO (service),
2141                                  error);
2142         if (!introspection)
2143                 return;
2144
2145         /* Get a handle on the main executable -- use this to find symbols */
2146         module = g_module_open (NULL, 0);
2147         if (module == NULL) {
2148                 g_error ("Failed to open module: %s", g_module_error ());
2149
2150                 g_object_unref (introspection);
2151
2152                 return;
2153         }
2154
2155         names = gupnp_service_introspection_list_action_names (introspection);
2156         connect_names_to_signal_handlers (service,
2157                                           module,
2158                                           names,
2159                                           "action-invoked",
2160                                           NULL,
2161                                           user_data);
2162
2163         names = gupnp_service_introspection_list_state_variable_names
2164                         (introspection);
2165         connect_names_to_signal_handlers (service,
2166                                           module,
2167                                           names,
2168                                           "query-variable",
2169                                           "query",
2170                                           user_data);
2171
2172         g_module_close (module);
2173         g_object_unref (introspection);
2174 }