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