2 * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
4 * Author: Jorn Baayen <jorn@openedhand.com>
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.
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.
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.
23 * SECTION:gupnp-service-proxy
24 * @short_description: Proxy class for remote services.
26 * #GUPnPServiceProxy sends commands to a remote UPnP service and handles
27 * incoming event notifications. #GUPnPServiceProxy implements the
28 * #GUPnPServiceInfo interface.
31 #include <libsoup/soup.h>
32 #include <gobject/gvaluecollector.h>
36 #include "gupnp-service-proxy.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-error.h"
39 #include "gupnp-error-private.h"
40 #include "gupnp-types.h"
42 #include "gena-protocol.h"
43 #include "http-headers.h"
44 #include "gvalue-util.h"
46 G_DEFINE_TYPE (GUPnPServiceProxy,
48 GUPNP_TYPE_SERVICE_INFO);
50 struct _GUPnPServiceProxyPrivate {
53 GList *pending_actions;
55 char *path; /* Path to this proxy */
57 char *sid; /* Subscription ID */
58 GSource *subscription_timeout_src;
60 int seq; /* Event sequence number */
62 GHashTable *notify_hash;
64 GList *pending_messages; /* Pending SoupMessages from this proxy */
66 GList *pending_notifies; /* Pending notifications to be sent (xmlDoc) */
67 GSource *notify_idle_src; /* Idle handler src of notification emiter */
80 static guint signals[LAST_SIGNAL];
82 struct _GUPnPServiceProxyAction {
83 GUPnPServiceProxy *proxy;
88 GUPnPServiceProxyActionCallback callback;
91 GError *error; /* If non-NULL, description of error that
92 occurred when preparing message */
94 va_list var_args; /* The va_list after begin_action_valist has
95 gone through it. Used by send_action_valist(). */
105 GUPnPServiceProxyNotifyCallback callback;
117 subscribe_got_response (SoupSession *session,
119 GUPnPServiceProxy *proxy);
121 subscribe (GUPnPServiceProxy *proxy);
123 unsubscribe (GUPnPServiceProxy *proxy);
126 gupnp_service_proxy_action_free (GUPnPServiceProxyAction *action)
128 action->proxy->priv->pending_actions =
129 g_list_remove (action->proxy->priv->pending_actions, action);
131 if (action->msg != NULL)
132 g_object_unref (action->msg);
134 g_slice_free (GUPnPServiceProxyAction, action);
138 notify_data_free (NotifyData *data)
140 g_list_free (data->callbacks);
142 g_slice_free (NotifyData, data);
145 /* Steals doc reference */
146 static EmitNotifyData *
147 emit_notify_data_new (const char *sid,
151 EmitNotifyData *data;
153 data = g_slice_new (EmitNotifyData);
155 data->sid = g_strdup (sid);
163 emit_notify_data_free (EmitNotifyData *data)
166 xmlFreeDoc (data->doc);
168 g_slice_free (EmitNotifyData, data);
172 gupnp_service_proxy_init (GUPnPServiceProxy *proxy)
174 static int proxy_counter = 0;
176 proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy,
177 GUPNP_TYPE_SERVICE_PROXY,
178 GUPnPServiceProxyPrivate);
180 /* Generate unique path */
181 proxy->priv->path = g_strdup_printf ("/ServiceProxy%d", proxy_counter);
184 /* Set up notify hash */
185 proxy->priv->notify_hash =
186 g_hash_table_new_full (g_str_hash,
189 (GDestroyNotify) notify_data_free);
193 gupnp_service_proxy_set_property (GObject *object,
198 GUPnPServiceProxy *proxy;
200 proxy = GUPNP_SERVICE_PROXY (object);
202 switch (property_id) {
203 case PROP_SUBSCRIBED:
204 gupnp_service_proxy_set_subscribed
205 (proxy, g_value_get_boolean (value));
208 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
214 gupnp_service_proxy_get_property (GObject *object,
219 GUPnPServiceProxy *proxy;
221 proxy = GUPNP_SERVICE_PROXY (object);
223 switch (property_id) {
224 case PROP_SUBSCRIBED:
225 g_value_set_boolean (value,
226 proxy->priv->subscribed);
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235 gupnp_service_proxy_dispose (GObject *object)
237 GUPnPServiceProxy *proxy;
238 GObjectClass *object_class;
239 GUPnPContext *context;
240 SoupSession *session;
242 proxy = GUPNP_SERVICE_PROXY (object);
245 if (proxy->priv->subscribed) {
248 proxy->priv->subscribed = FALSE;
251 /* Cancel pending actions */
252 while (proxy->priv->pending_actions) {
253 GUPnPServiceProxyAction *action;
255 action = proxy->priv->pending_actions->data;
257 gupnp_service_proxy_cancel_action (proxy, action);
260 /* Cancel pending messages */
261 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
263 session = gupnp_context_get_session (context);
265 session = NULL; /* Not the first time dispose is called. */
267 while (proxy->priv->pending_messages) {
270 msg = proxy->priv->pending_messages->data;
272 soup_session_cancel_message (session,
274 SOUP_STATUS_CANCELLED);
276 proxy->priv->pending_messages =
277 g_list_delete_link (proxy->priv->pending_messages,
278 proxy->priv->pending_messages);
281 /* Cancel pending notifications */
282 if (proxy->priv->notify_idle_src) {
283 g_source_destroy (proxy->priv->notify_idle_src);
284 proxy->priv->notify_idle_src = NULL;
287 while (proxy->priv->pending_notifies) {
288 emit_notify_data_free (proxy->priv->pending_notifies->data);
289 proxy->priv->pending_notifies =
290 g_list_delete_link (proxy->priv->pending_notifies,
291 proxy->priv->pending_notifies);
295 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
296 object_class->dispose (object);
300 gupnp_service_proxy_finalize (GObject *object)
302 GUPnPServiceProxy *proxy;
303 GObjectClass *object_class;
305 proxy = GUPNP_SERVICE_PROXY (object);
307 g_free (proxy->priv->path);
309 g_hash_table_destroy (proxy->priv->notify_hash);
312 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
313 object_class->finalize (object);
317 gupnp_service_proxy_class_init (GUPnPServiceProxyClass *klass)
319 GObjectClass *object_class;
321 object_class = G_OBJECT_CLASS (klass);
323 object_class->set_property = gupnp_service_proxy_set_property;
324 object_class->get_property = gupnp_service_proxy_get_property;
325 object_class->dispose = gupnp_service_proxy_dispose;
326 object_class->finalize = gupnp_service_proxy_finalize;
328 g_type_class_add_private (klass, sizeof (GUPnPServiceProxyPrivate));
331 * GUPnPServiceProxy:subscribed:
333 * Whether we are subscribed to this service.
335 g_object_class_install_property
338 g_param_spec_boolean ("subscribed",
340 "Whether we are subscribed to this "
344 G_PARAM_STATIC_NAME |
345 G_PARAM_STATIC_NICK |
346 G_PARAM_STATIC_BLURB));
349 * GUPnPServiceProxy::subscription-lost:
350 * @proxy: The #GUPnPServiceProxy that received the signal
351 * @error: (type GError):A pointer to a #GError describing why the subscription has
354 * Emitted whenever the subscription to this service has been lost due
355 * to an error condition.
357 signals[SUBSCRIPTION_LOST] =
358 g_signal_new ("subscription-lost",
359 GUPNP_TYPE_SERVICE_PROXY,
361 G_STRUCT_OFFSET (GUPnPServiceProxyClass,
365 g_cclosure_marshal_VOID__POINTER,
372 * gupnp_service_proxy_send_action:
373 * @proxy: A #GUPnPServiceProxy
375 * @error: The location where to store any error, or %NULL
376 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
377 * value, followed by %NULL, and then tuples of out parameter name,
378 * out parameter type, and out parameter value location, terminated with %NULL
380 * Sends action @action with parameters @Varargs to the service exposed by
381 * @proxy synchronously. If an error occurred, @error will be set. In case of
382 * a UPnPError the error code will be the same in @error.
384 * Return value: %TRUE if sending the action was succesful.
387 gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
395 va_start (var_args, error);
396 ret = gupnp_service_proxy_send_action_valist (proxy,
406 stop_main_loop (GUPnPServiceProxy *proxy,
407 GUPnPServiceProxyAction *action,
410 g_main_loop_quit ((GMainLoop *) user_data);
414 * gupnp_service_proxy_send_action_valist:
415 * @proxy: A #GUPnPServiceProxy
417 * @error: The location where to store any error, or %NULL
418 * @var_args: va_list of tuples of in parameter name, in parameter type, and in
419 * parameter value, followed by %NULL, and then tuples of out parameter name,
420 * out parameter type, and out parameter value location
422 * See gupnp_service_proxy_send_action(); this version takes a va_list for
423 * use by language bindings.
425 * Return value: %TRUE if sending the action was succesful.
428 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
433 GMainLoop *main_loop;
434 GUPnPServiceProxyAction *handle;
436 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
437 g_return_val_if_fail (action, FALSE);
439 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
442 handle = gupnp_service_proxy_begin_action_valist (proxy,
448 /* Loop till we get a reply (or time out) */
449 if (g_main_loop_is_running (main_loop))
450 g_main_loop_run (main_loop);
452 g_main_loop_unref (main_loop);
454 if (!gupnp_service_proxy_end_action_valist (proxy,
464 * gupnp_service_proxy_send_action_hash:
465 * @proxy: A #GUPnPServiceProxy
467 * @error: The location where to store any error, or %NULL
468 * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
469 * parameter name and #GValue pairs
470 * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
471 * of out parameter name and initialized #GValue pairs
473 * See gupnp_service_proxy_send_action(); this version takes a pair of
474 * #GHashTable<!-- -->s for runtime determined parameter lists.
476 * Return value: %TRUE if sending the action was succesful.
479 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
483 GHashTable *out_hash)
485 GMainLoop *main_loop;
486 GUPnPServiceProxyAction *handle;
488 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
489 g_return_val_if_fail (action, FALSE);
491 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
494 handle = gupnp_service_proxy_begin_action_hash (proxy,
500 g_main_loop_unref (main_loop);
505 /* Loop till we get a reply (or time out) */
506 if (g_main_loop_is_running (main_loop))
507 g_main_loop_run (main_loop);
509 g_main_loop_unref (main_loop);
511 if (!gupnp_service_proxy_end_action_hash (proxy,
521 * gupnp_service_proxy_send_action_list:
522 * @proxy: (transfer none) : A #GUPnPServiceProxy
524 * @error: The location where to store any error, or %NULL
525 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
527 * @in_values: (element-type GValue) (transfer none): #GList of values (as
528 * #GValue) that line up with @in_names
529 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
531 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
532 * that line up with @out_names
533 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
534 * (as #GValue) that line up with @out_names and @out_types.
536 * The synchronous variant of #gupnp_service_proxy_begin_action_list and
537 * #gupnp_service_proxy_end_action_list.
539 * Return value: %TRUE if sending the action was succesful.
542 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
551 GMainLoop *main_loop;
552 GUPnPServiceProxyAction *handle;
554 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
555 g_return_val_if_fail (action, FALSE);
557 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
560 handle = gupnp_service_proxy_begin_action_list (proxy,
567 g_main_loop_unref (main_loop);
572 /* Loop till we get a reply (or time out) */
573 if (g_main_loop_is_running (main_loop))
574 g_main_loop_run (main_loop);
576 g_main_loop_unref (main_loop);
578 if (!gupnp_service_proxy_end_action_list (proxy,
590 * gupnp_service_proxy_begin_action:
591 * @proxy: A #GUPnPServiceProxy
593 * @callback: (scope async): The callback to call when sending the action has succeeded
595 * @user_data: User data for @callback
596 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
597 * value, terminated with %NULL
599 * Sends action @action with parameters @Varargs to the service exposed by
600 * @proxy asynchronously, calling @callback on completion. From @callback, call
601 * gupnp_service_proxy_end_action() to check for errors, to retrieve return
602 * values, and to free the #GUPnPServiceProxyAction.
604 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
605 * gupnp_service_proxy_cancel_action() or
606 * gupnp_service_proxy_end_action_valist().
608 GUPnPServiceProxyAction *
609 gupnp_service_proxy_begin_action (GUPnPServiceProxy *proxy,
611 GUPnPServiceProxyActionCallback callback,
616 GUPnPServiceProxyAction *ret;
618 va_start (var_args, user_data);
619 ret = gupnp_service_proxy_begin_action_valist (proxy,
629 /* Begins a basic action message */
630 static GUPnPServiceProxyAction *
631 begin_action_msg (GUPnPServiceProxy *proxy,
633 GUPnPServiceProxyActionCallback callback,
636 GUPnPServiceProxyAction *ret;
637 char *control_url, *full_action;
638 const char *service_type;
640 /* Create action structure */
641 ret = g_slice_new (GUPnPServiceProxyAction);
645 ret->callback = callback;
646 ret->user_data = user_data;
652 proxy->priv->pending_actions =
653 g_list_prepend (proxy->priv->pending_actions, ret);
655 /* Make sure we have a service type */
656 service_type = gupnp_service_info_get_service_type
657 (GUPNP_SERVICE_INFO (proxy));
658 if (service_type == NULL) {
659 ret->error = g_error_new (GUPNP_SERVER_ERROR,
660 GUPNP_SERVER_ERROR_OTHER,
661 "No service type defined");
667 control_url = gupnp_service_info_get_control_url
668 (GUPNP_SERVICE_INFO (proxy));
670 if (control_url != NULL) {
671 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
673 g_free (control_url);
676 if (ret->msg == NULL) {
677 ret->error = g_error_new (GUPNP_SERVER_ERROR,
678 GUPNP_SERVER_ERROR_INVALID_URL,
679 "No valid control URL defined");
685 full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
686 soup_message_headers_append (ret->msg->request_headers,
689 g_free (full_action);
691 /* Specify language */
692 http_request_set_accept_language (ret->msg);
694 /* Accept gzip encoding */
695 soup_message_headers_append (ret->msg->request_headers,
696 "Accept-Encoding", "gzip");
698 /* Set up envelope */
699 ret->msg_str = xml_util_new_string ();
701 g_string_append (ret->msg_str,
702 "<?xml version=\"1.0\"?>"
703 "<s:Envelope xmlns:s="
704 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
706 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
709 g_string_append (ret->msg_str, "<u:");
710 g_string_append (ret->msg_str, action);
711 g_string_append (ret->msg_str, " xmlns:u=\"");
712 g_string_append (ret->msg_str, service_type);
713 g_string_append (ret->msg_str, "\">");
718 /* Received response to action message */
720 action_got_response (SoupSession *session,
722 GUPnPServiceProxyAction *action)
724 const char *full_action;
726 switch (msg->status_code) {
727 case SOUP_STATUS_CANCELLED:
728 /* Silently return */
731 case SOUP_STATUS_METHOD_NOT_ALLOWED:
732 /* Retry with M-POST */
733 msg->method = "M-POST";
735 soup_message_headers_append
736 (msg->request_headers,
738 "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
740 /* Rename "SOAPAction" to "s-SOAPAction" */
741 full_action = soup_message_headers_get_one
742 (msg->request_headers,
744 soup_message_headers_append (msg->request_headers,
747 soup_message_headers_remove (msg->request_headers,
751 soup_session_requeue_message (session, msg);
756 /* Success: Call callback */
757 action->callback (action->proxy, action, action->user_data);
763 /* Finishes an action message and sends it off */
765 finish_action_msg (GUPnPServiceProxyAction *action,
766 const char *action_name)
768 GUPnPContext *context;
769 SoupSession *session;
772 g_string_append (action->msg_str, "</u:");
773 g_string_append (action->msg_str, action_name);
774 g_string_append_c (action->msg_str, '>');
776 g_string_append (action->msg_str,
780 soup_message_set_request (action->msg,
781 "text/xml; charset=\"utf-8\"",
783 action->msg_str->str,
784 action->msg_str->len);
786 g_string_free (action->msg_str, FALSE);
788 /* We need to keep our own reference to the message as well,
789 * in order for send_action() to work. */
790 g_object_ref (action->msg);
792 /* Send the message */
793 context = gupnp_service_info_get_context
794 (GUPNP_SERVICE_INFO (action->proxy));
795 session = gupnp_context_get_session (context);
797 soup_session_queue_message (session,
799 (SoupSessionCallback) action_got_response,
803 /* Writes a parameter name and GValue pair to @msg */
805 write_in_parameter (const char *arg_name,
809 /* Write parameter pair */
810 xml_util_start_element (msg_str, arg_name);
811 gvalue_util_value_append_to_xml_string (value, msg_str);
812 xml_util_end_element (msg_str, arg_name);
816 * gupnp_service_proxy_begin_action_valist:
817 * @proxy: A #GUPnPServiceProxy
819 * @callback: (scope async) : The callback to call when sending the action has succeeded
821 * @user_data: User data for @callback
822 * @var_args: A va_list of tuples of in parameter name, in parameter type, and
825 * See gupnp_service_proxy_begin_action(); this version takes a va_list for
826 * use by language bindings.
828 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
829 * be freed when calling gupnp_service_proxy_cancel_action() or
830 * gupnp_service_proxy_end_action_valist().
832 GUPnPServiceProxyAction *
833 gupnp_service_proxy_begin_action_valist
834 (GUPnPServiceProxy *proxy,
836 GUPnPServiceProxyActionCallback callback,
840 const char *arg_name;
841 GUPnPServiceProxyAction *ret;
843 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
844 g_return_val_if_fail (action, NULL);
845 g_return_val_if_fail (callback, NULL);
848 ret = begin_action_msg (proxy, action, callback, user_data);
851 callback (proxy, ret, user_data);
857 arg_name = va_arg (var_args, const char *);
860 GValue value = { 0, };
861 char *collect_error = NULL;
863 arg_type = va_arg (var_args, GType);
864 g_value_init (&value, arg_type);
866 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
868 if (!collect_error) {
869 write_in_parameter (arg_name, &value, ret->msg_str);
871 g_value_unset (&value);
874 g_warning ("Error collecting value: %s\n",
877 g_free (collect_error);
879 /* we purposely leak the value here, it might not be
880 * in a sane state if an error condition occoured
884 arg_name = va_arg (var_args, const char *);
887 /* Finish and send off */
888 finish_action_msg (ret, action);
890 /* Save the current position in the va_list for send_action_valist() */
891 G_VA_COPY (ret->var_args, var_args);
897 * gupnp_service_proxy_begin_action_list:
898 * @proxy: (transfer none): A #GUPnPServiceProxy
900 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
902 * @in_values: (element-type GValue) (transfer none): #GList of values (as
903 * #GValue) that line up with @in_names
904 * @callback: (scope async) : The callback to call when sending the action has succeeded or
906 * @user_data: User data for @callback
908 * A variant of #gupnp_service_proxy_begin_action that takes lists of
909 * in-parameter names, types and values.
911 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
912 * be freed when calling #gupnp_service_proxy_cancel_action or
913 * #gupnp_service_proxy_end_action_list.
915 GUPnPServiceProxyAction *
916 gupnp_service_proxy_begin_action_list
917 (GUPnPServiceProxy *proxy,
921 GUPnPServiceProxyActionCallback callback,
924 GUPnPServiceProxyAction *ret;
928 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
929 g_return_val_if_fail (action, NULL);
930 g_return_val_if_fail (callback, NULL);
931 g_return_val_if_fail (g_list_length (in_names) ==
932 g_list_length (in_values),
936 ret = begin_action_msg (proxy, action, callback, user_data);
939 callback (proxy, ret, user_data);
947 for (names = in_names; names; names=names->next) {
948 GValue* val = values->data;
950 write_in_parameter (names->data,
954 values = values->next;
957 /* Finish and send off */
958 finish_action_msg (ret, action);
964 * gupnp_service_proxy_begin_action_hash:
965 * @proxy: A #GUPnPServiceProxy
967 * @callback: (scope async): The callback to call when sending the action has succeeded
969 * @user_data: User data for @callback
970 * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
972 * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
973 * for runtime generated parameter lists.
975 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
976 * be freed when calling gupnp_service_proxy_cancel_action() or
977 * gupnp_service_proxy_end_action_hash().
979 GUPnPServiceProxyAction *
980 gupnp_service_proxy_begin_action_hash
981 (GUPnPServiceProxy *proxy,
983 GUPnPServiceProxyActionCallback callback,
987 GUPnPServiceProxyAction *ret;
989 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
990 g_return_val_if_fail (action, NULL);
991 g_return_val_if_fail (callback, NULL);
994 ret = begin_action_msg (proxy, action, callback, user_data);
997 callback (proxy, ret, user_data);
1003 g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1005 /* Finish and send off */
1006 finish_action_msg (ret, action);
1012 * gupnp_service_proxy_end_action:
1013 * @proxy: A #GUPnPServiceProxy
1014 * @action: A #GUPnPServiceProxyAction handle
1015 * @error: The location where to store any error, or %NULL
1016 * @Varargs: tuples of out parameter name, out parameter type, and out parameter
1017 * value location, terminated with %NULL. The out parameter values should be
1020 * Retrieves the result of @action. The out parameters in @Varargs will be
1021 * filled in, and if an error occurred, @error will be set. In case of
1022 * a UPnPError the error code will be the same in @error.
1024 * Return value: %TRUE on success.
1027 gupnp_service_proxy_end_action (GUPnPServiceProxy *proxy,
1028 GUPnPServiceProxyAction *action,
1035 va_start (var_args, error);
1036 ret = gupnp_service_proxy_end_action_valist (proxy,
1045 /* Checks an action response for errors and returns the parsed
1048 check_action_response (GUPnPServiceProxy *proxy,
1049 GUPnPServiceProxyAction *action,
1056 /* Check for errors */
1057 switch (action->msg->status_code) {
1058 case SOUP_STATUS_OK:
1059 case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1062 _gupnp_error_set_server_error (error, action->msg);
1067 /* Parse response */
1068 response = xmlRecoverMemory (action->msg->response_body->data,
1069 action->msg->response_body->length);
1072 if (action->msg->status_code == SOUP_STATUS_OK) {
1075 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1076 "Could not parse SOAP response");
1081 GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1082 action->msg->reason_phrase);
1088 /* Get parameter list */
1089 *params = xml_util_get_element ((xmlNode *) response,
1092 if (*params != NULL)
1093 *params = xml_util_real_node ((*params)->children);
1095 if (*params != NULL) {
1096 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1097 *params = xml_util_real_node ((*params)->next);
1099 if (*params != NULL)
1100 if (strcmp ((const char *) (*params)->name, "Body") != 0)
1104 if (*params != NULL)
1105 *params = xml_util_real_node ((*params)->children);
1107 if (*params == NULL) {
1110 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1111 "Invalid Envelope");
1113 xmlFreeDoc (response);
1118 /* Check whether we have a Fault */
1119 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1123 param = xml_util_get_element (*params,
1131 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1134 xmlFreeDoc (response);
1140 code = xml_util_get_child_element_content_int
1141 (param, "errorCode");
1145 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1148 xmlFreeDoc (response);
1154 desc = xml_util_get_child_element_content_glib
1155 (param, "errorDescription");
1157 desc = g_strdup (action->msg->reason_phrase);
1159 g_set_error_literal (error,
1160 GUPNP_CONTROL_ERROR,
1166 xmlFreeDoc (response);
1174 /* Reads a value into the parameter name and initialised GValue pair
1177 read_out_parameter (const char *arg_name,
1183 /* Try to find a matching parameter in the response*/
1184 param = xml_util_get_element (params,
1188 g_warning ("Could not find variable \"%s\" in response",
1194 gvalue_util_set_value_from_xml_node (value, param);
1198 * gupnp_service_proxy_end_action_valist:
1199 * @proxy: A #GUPnPServiceProxy
1200 * @action: A #GUPnPServiceProxyAction handle
1201 * @error: The location where to store any error, or %NULL
1202 * @var_args: A va_list of tuples of out parameter name, out parameter type,
1203 * and out parameter value location. The out parameter values should be
1206 * See gupnp_service_proxy_end_action(); this version takes a va_list for
1207 * use by language bindings.
1209 * Return value: %TRUE on success.
1212 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy *proxy,
1213 GUPnPServiceProxyAction *action,
1219 const char *arg_name;
1221 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1222 g_return_val_if_fail (action, FALSE);
1223 g_return_val_if_fail (proxy == action->proxy, FALSE);
1225 /* Check for saved error from begin_action() */
1226 if (action->error) {
1228 *error = action->error;
1230 g_error_free (action->error);
1232 gupnp_service_proxy_action_free (action);
1237 /* Check response for errors and do initial parsing */
1238 response = check_action_response (proxy, action, ¶ms, error);
1239 if (response == NULL) {
1240 gupnp_service_proxy_action_free (action);
1246 arg_name = va_arg (var_args, const char *);
1249 GValue value = { 0, };
1250 char *copy_error = NULL;
1252 arg_type = va_arg (var_args, GType);
1254 g_value_init (&value, arg_type);
1256 read_out_parameter (arg_name, &value, params);
1258 G_VALUE_LCOPY (&value, var_args, 0, ©_error);
1260 g_value_unset (&value);
1263 g_warning ("Error copying value: %s", copy_error);
1265 g_free (copy_error);
1268 arg_name = va_arg (var_args, const char *);
1272 gupnp_service_proxy_action_free (action);
1274 xmlFreeDoc (response);
1280 * gupnp_service_proxy_end_action_list:
1281 * @proxy: A #GUPnPServiceProxy
1282 * @action: A #GUPnPServiceProxyAction handle
1283 * @error: The location where to store any error, or %NULL
1284 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1285 * names (as strings)
1286 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1287 * that line up with @out_names
1288 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1289 * (as #GValue) that line up with @out_names and @out_types.
1291 * A variant of #gupnp_service_proxy_end_action that takes lists of
1292 * out-parameter names, types and place-holders for values. The returned list
1293 * in @out_values must be freed using #g_list_free and each element in it using
1294 * #g_value_unset and #g_slice_free.
1296 * Return value : %TRUE on success.
1299 gupnp_service_proxy_end_action_list (GUPnPServiceProxy *proxy,
1300 GUPnPServiceProxyAction *action,
1310 GList *out_values_list;
1312 out_values_list = NULL;
1314 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1315 g_return_val_if_fail (action, FALSE);
1316 g_return_val_if_fail (proxy == action->proxy, FALSE);
1318 /* Check for saved error from begin_action() */
1319 if (action->error) {
1321 *error = action->error;
1323 g_error_free (action->error);
1325 gupnp_service_proxy_action_free (action);
1330 /* Check response for errors and do initial parsing */
1331 response = check_action_response (proxy, action, ¶ms, error);
1332 if (response == NULL) {
1333 gupnp_service_proxy_action_free (action);
1338 /* Read arguments */
1340 for (names = out_names; names; names=names->next) {
1343 val = g_slice_new0 (GValue);
1344 g_value_init (val, (GType) types->data);
1346 read_out_parameter (names->data, val, params);
1348 out_values_list = g_list_append (out_values_list, val);
1350 types = types->next;
1353 *out_values = out_values_list;
1356 gupnp_service_proxy_action_free (action);
1358 xmlFreeDoc (response);
1364 * gupnp_service_proxy_end_action_hash:
1365 * @proxy: A #GUPnPServiceProxy
1366 * @action: A #GUPnPServiceProxyAction handle
1367 * @error: The location where to store any error, or %NULL
1368 * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1369 * out parameter name and initialised #GValue pairs
1371 * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1372 * runtime generated parameter lists.
1374 * Return value: %TRUE on success.
1377 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy *proxy,
1378 GUPnPServiceProxyAction *action,
1385 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1386 g_return_val_if_fail (action, FALSE);
1387 g_return_val_if_fail (proxy == action->proxy, FALSE);
1389 /* Check for saved error from begin_action() */
1390 if (action->error) {
1392 *error = action->error;
1394 g_error_free (action->error);
1396 gupnp_service_proxy_action_free (action);
1401 /* Check response for errors and do initial parsing */
1402 response = check_action_response (proxy, action, ¶ms, error);
1403 if (response == NULL) {
1404 gupnp_service_proxy_action_free (action);
1409 /* Read arguments */
1410 g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1413 gupnp_service_proxy_action_free (action);
1415 xmlFreeDoc (response);
1421 * gupnp_service_proxy_cancel_action:
1422 * @proxy: A #GUPnPServiceProxy
1423 * @action: A #GUPnPServiceProxyAction handle
1425 * Cancels @action, freeing the @action handle.
1428 gupnp_service_proxy_cancel_action (GUPnPServiceProxy *proxy,
1429 GUPnPServiceProxyAction *action)
1431 GUPnPContext *context;
1432 SoupSession *session;
1434 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1435 g_return_if_fail (action);
1436 g_return_if_fail (proxy == action->proxy);
1438 if (action->msg != NULL) {
1439 context = gupnp_service_info_get_context
1440 (GUPNP_SERVICE_INFO (proxy));
1441 session = gupnp_context_get_session (context);
1443 soup_session_cancel_message (session,
1445 SOUP_STATUS_CANCELLED);
1448 if (action->error != NULL)
1449 g_error_free (action->error);
1451 gupnp_service_proxy_action_free (action);
1455 * gupnp_service_proxy_add_notify:
1456 * @proxy: A #GUPnPServiceProxy
1457 * @variable: The variable to add notification for
1458 * @type: The type of the variable
1459 * @callback: (scope async): The callback to call when @variable changes
1460 * @user_data: User data for @callback
1462 * Sets up @callback to be called whenever a change notification for
1463 * @variable is recieved.
1465 * Return value: %TRUE on success.
1468 gupnp_service_proxy_add_notify (GUPnPServiceProxy *proxy,
1469 const char *variable,
1471 GUPnPServiceProxyNotifyCallback callback,
1475 CallbackData *callback_data;
1477 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1478 g_return_val_if_fail (variable, FALSE);
1479 g_return_val_if_fail (type, FALSE);
1480 g_return_val_if_fail (callback, FALSE);
1482 /* See if we already have notifications set up for this variable */
1483 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1485 /* No, create one */
1486 data = g_slice_new (NotifyData);
1489 data->callbacks = NULL;
1491 g_hash_table_insert (proxy->priv->notify_hash,
1492 g_strdup (variable),
1495 /* Yes, check that everything is sane .. */
1496 if (data->type != type) {
1497 g_warning ("A notification already exists for %s, but "
1498 "has type %s, not %s.",
1500 g_type_name (data->type),
1501 g_type_name (type));
1507 /* Append callback */
1508 callback_data = g_slice_new (CallbackData);
1510 callback_data->callback = callback;
1511 callback_data->user_data = user_data;
1513 data->callbacks = g_list_append (data->callbacks, callback_data);
1519 * gupnp_service_proxy_remove_notify:
1520 * @proxy: A #GUPnPServiceProxy
1521 * @variable: The variable to add notification for
1522 * @callback: (scope call): The callback to call when @variable changes
1523 * @user_data: User data for @callback
1525 * Cancels the variable change notification for @callback and @user_data.
1527 * Return value: %TRUE on success.
1530 gupnp_service_proxy_remove_notify (GUPnPServiceProxy *proxy,
1531 const char *variable,
1532 GUPnPServiceProxyNotifyCallback callback,
1539 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1540 g_return_val_if_fail (variable, FALSE);
1541 g_return_val_if_fail (callback, FALSE);
1543 /* Look up NotifyData for variable */
1544 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1546 g_warning ("No notifications found for variable %s",
1552 /* Look up our callback-user_data pair */
1555 for (l = data->callbacks; l; l = l->next) {
1556 CallbackData *callback_data;
1558 callback_data = l->data;
1560 if (callback_data->callback == callback &&
1561 callback_data->user_data == user_data) {
1563 g_slice_free (CallbackData, callback_data);
1566 g_list_delete_link (data->callbacks, l);
1567 if (data->callbacks == NULL) {
1568 /* No callbacks left: Remove from hash */
1569 g_hash_table_remove (proxy->priv->notify_hash,
1580 g_warning ("No such callback-user_data pair was found");
1586 emit_notification (GUPnPServiceProxy *proxy,
1590 GValue value = {0, };
1593 data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1597 /* Make a GValue of the desired type */
1598 g_value_init (&value, data->type);
1600 if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1601 g_value_unset (&value);
1606 /* Call callbacks */
1607 for (l = data->callbacks; l; l = l->next) {
1608 CallbackData *callback_data;
1610 callback_data = l->data;
1612 callback_data->callback (proxy,
1613 (const char *) var_node->name,
1615 callback_data->user_data);
1619 g_value_unset (&value);
1623 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1628 node = xmlDocGetRootElement (doc);
1630 /* Iterate over all provided properties */
1631 for (node = node->children; node; node = node->next) {
1634 /* Although according to the UPnP specs, there should be only
1635 * one variable node inside a 'property' node, we still need to
1636 * entertain the possibility of multiple variables inside it to
1637 * be compatible with implementations using older GUPnP.
1639 for (var_node = node->children;
1641 var_node = var_node->next) {
1642 if (strcmp ((char *) node->name, "property") == 0)
1643 emit_notification (proxy, var_node);
1648 /* Emit pending notifications. See comment below on why we do this. */
1650 emit_notifications (gpointer user_data)
1652 GUPnPServiceProxy *proxy = user_data;
1653 GList *pending_notify;
1654 gboolean resubscribe = FALSE;
1656 g_assert (user_data);
1658 if (proxy->priv->sid == NULL)
1660 if (G_LIKELY (proxy->priv->subscribed))
1661 /* subscription in progress, delay emision! */
1664 for (pending_notify = proxy->priv->pending_notifies;
1665 pending_notify != NULL;
1666 pending_notify = pending_notify->next) {
1667 EmitNotifyData *emit_notify_data;
1669 emit_notify_data = pending_notify->data;
1671 if (emit_notify_data->seq > proxy->priv->seq) {
1672 /* Oops, we missed a notify. Resubscribe .. */
1678 /* Increment our own event sequence number */
1679 if (proxy->priv->seq < G_MAXINT32)
1682 proxy->priv->seq = 1;
1684 if (G_LIKELY (proxy->priv->sid != NULL &&
1685 strcmp (emit_notify_data->sid,
1686 proxy->priv->sid) == 0))
1687 /* Our SID, entertain! */
1688 emit_notifications_for_doc (proxy,
1689 emit_notify_data->doc);
1693 while (proxy->priv->pending_notifies != NULL) {
1694 emit_notify_data_free (proxy->priv->pending_notifies->data);
1696 proxy->priv->pending_notifies =
1697 g_list_delete_link (proxy->priv->pending_notifies,
1698 proxy->priv->pending_notifies);
1701 proxy->priv->notify_idle_src = NULL;
1704 unsubscribe (proxy);
1712 * HTTP server received a message. Handle, if this was a NOTIFY
1713 * message with our SID.
1716 server_handler (SoupServer *soup_server,
1718 const char *server_path,
1720 SoupClientContext *soup_client,
1723 GUPnPServiceProxy *proxy;
1728 EmitNotifyData *emit_notify_data;
1730 proxy = GUPNP_SERVICE_PROXY (user_data);
1732 if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1733 /* We don't implement this method */
1734 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1739 hdr = soup_message_headers_get_one (msg->request_headers, "NT");
1740 if (hdr == NULL || strcmp (hdr, "upnp:event") != 0) {
1741 /* Proper NT header lacking */
1742 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1747 hdr = soup_message_headers_get_one (msg->request_headers, "NTS");
1748 if (hdr == NULL || strcmp (hdr, "upnp:propchange") != 0) {
1749 /* Proper NTS header lacking */
1750 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1755 hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1758 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1765 hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1768 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1773 /* Parse the actual XML message content */
1774 doc = xmlRecoverMemory (msg->request_body->data,
1775 msg->request_body->length);
1778 g_warning ("Failed to parse NOTIFY message body");
1780 soup_message_set_status (msg,
1781 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1786 /* Get root propertyset element */
1787 node = xmlDocGetRootElement (doc);
1788 if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1789 /* Empty or unsupported */
1792 soup_message_set_status (msg, SOUP_STATUS_OK);
1798 * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1799 * call the callbacks in an idle handler so that if the client calls the
1800 * device in the notify callback the server can actually respond.
1802 emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1804 proxy->priv->pending_notifies =
1805 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1806 if (!proxy->priv->notify_idle_src) {
1807 proxy->priv->notify_idle_src = g_idle_source_new();
1808 g_source_set_callback (proxy->priv->notify_idle_src,
1811 g_source_attach (proxy->priv->notify_idle_src,
1812 g_main_context_get_thread_default ());
1814 g_source_unref (proxy->priv->notify_idle_src);
1817 /* Everything went OK */
1818 soup_message_set_status (msg, SOUP_STATUS_OK);
1822 * Generates a timeout header for the subscription timeout specified
1823 * in our GUPnPContext.
1826 make_timeout_header (GUPnPContext *context)
1830 timeout = gupnp_context_get_subscription_timeout (context);
1832 return g_strdup_printf ("Second-%d", timeout);
1834 return g_strdup ("infinite");
1838 * Subscription expired.
1841 subscription_expire (gpointer user_data)
1843 GUPnPServiceProxy *proxy;
1844 GUPnPContext *context;
1846 SoupSession *session;
1847 char *sub_url, *timeout;
1849 proxy = GUPNP_SERVICE_PROXY (user_data);
1851 /* Reset timeout ID */
1852 proxy->priv->subscription_timeout_src = NULL;
1854 /* Send renewal message */
1855 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1857 /* Create subscription message */
1858 sub_url = gupnp_service_info_get_event_subscription_url
1859 (GUPNP_SERVICE_INFO (proxy));
1861 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1865 g_return_val_if_fail (msg != NULL, FALSE);
1868 soup_message_headers_append (msg->request_headers,
1872 timeout = make_timeout_header (context);
1873 soup_message_headers_append (msg->request_headers,
1878 /* And send it off */
1879 proxy->priv->pending_messages =
1880 g_list_prepend (proxy->priv->pending_messages, msg);
1882 session = gupnp_context_get_session (context);
1884 soup_session_queue_message (session,
1886 (SoupSessionCallback)
1887 subscribe_got_response,
1894 * Received subscription response.
1897 subscribe_got_response (SoupSession *session,
1899 GUPnPServiceProxy *proxy)
1904 if (msg->status_code == SOUP_STATUS_CANCELLED)
1907 /* Remove from pending messages list */
1908 proxy->priv->pending_messages =
1909 g_list_remove (proxy->priv->pending_messages, msg);
1911 /* Check whether the subscription is still wanted */
1912 if (!proxy->priv->subscribed)
1916 g_free (proxy->priv->sid);
1917 proxy->priv->sid = NULL;
1919 /* Check message status */
1920 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1926 hdr = soup_message_headers_get_one (msg->response_headers,
1930 (GUPNP_EVENTING_ERROR,
1931 GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
1932 "No SID in SUBSCRIBE response");
1937 proxy->priv->sid = g_strdup (hdr);
1939 /* Figure out when the subscription times out */
1940 hdr = soup_message_headers_get_one (msg->response_headers,
1943 g_warning ("No Timeout in SUBSCRIBE response.");
1948 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
1949 /* We have a finite timeout */
1950 timeout = atoi (hdr + strlen ("Second-"));
1953 g_warning ("Invalid time-out specified. "
1954 "Assuming default value of %d.",
1955 GENA_DEFAULT_TIMEOUT);
1957 timeout = GENA_DEFAULT_TIMEOUT;
1960 /* We want to resubscribe before the subscription
1962 timeout = g_random_int_range (1, timeout / 2);
1964 /* Add actual timeout */
1965 proxy->priv->subscription_timeout_src =
1966 g_timeout_source_new_seconds (timeout);
1967 g_source_set_callback
1968 (proxy->priv->subscription_timeout_src,
1969 subscription_expire,
1971 g_source_attach (proxy->priv->subscription_timeout_src,
1972 g_main_context_get_thread_default ());
1974 g_source_unref (proxy->priv->subscription_timeout_src);
1977 GUPnPContext *context;
1980 /* Subscription failed. */
1981 error = g_error_new_literal
1982 (GUPNP_EVENTING_ERROR,
1983 GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
1984 msg->reason_phrase);
1987 /* Remove listener */
1988 context = gupnp_service_info_get_context
1989 (GUPNP_SERVICE_INFO (proxy));
1991 server = gupnp_context_get_server (context);
1992 soup_server_remove_handler (server, proxy->priv->path);
1994 proxy->priv->subscribed = FALSE;
1996 g_object_notify (G_OBJECT (proxy), "subscribed");
1998 /* Emit subscription-lost */
1999 g_signal_emit (proxy,
2000 signals[SUBSCRIPTION_LOST],
2004 g_error_free (error);
2009 * Subscribe to this service.
2012 subscribe (GUPnPServiceProxy *proxy)
2014 GUPnPContext *context;
2016 SoupSession *session;
2018 const char *server_url;
2019 char *sub_url, *delivery_url, *timeout;
2021 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2023 /* Create subscription message */
2024 sub_url = gupnp_service_info_get_event_subscription_url
2025 (GUPNP_SERVICE_INFO (proxy));
2028 if (sub_url != NULL) {
2029 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2037 /* Subscription failed. */
2038 proxy->priv->subscribed = FALSE;
2040 g_object_notify (G_OBJECT (proxy), "subscribed");
2042 /* Emit subscription-lost */
2043 error = g_error_new (GUPNP_SERVER_ERROR,
2044 GUPNP_SERVER_ERROR_INVALID_URL,
2045 "No valid subscription URL defined");
2047 g_signal_emit (proxy,
2048 signals[SUBSCRIPTION_LOST],
2052 g_error_free (error);
2058 server_url = _gupnp_context_get_server_url (context);
2059 delivery_url = g_strdup_printf ("<%s%s>",
2062 soup_message_headers_append (msg->request_headers,
2065 g_free (delivery_url);
2067 soup_message_headers_append (msg->request_headers,
2071 timeout = make_timeout_header (context);
2072 soup_message_headers_append (msg->request_headers,
2077 /* Listen for events */
2078 server = gupnp_context_get_server (context);
2080 soup_server_add_handler (server,
2086 /* And send our subscription message off */
2087 proxy->priv->pending_messages =
2088 g_list_prepend (proxy->priv->pending_messages, msg);
2090 session = gupnp_context_get_session (context);
2092 soup_session_queue_message (session,
2094 (SoupSessionCallback)
2095 subscribe_got_response,
2100 * Unsubscribe from this service.
2103 unsubscribe (GUPnPServiceProxy *proxy)
2105 GUPnPContext *context;
2106 SoupSession *session;
2109 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2111 /* Remove server handler */
2112 server = gupnp_context_get_server (context);
2113 soup_server_remove_handler (server, proxy->priv->path);
2115 if (proxy->priv->sid != NULL) {
2119 /* Create unsubscription message */
2120 sub_url = gupnp_service_info_get_event_subscription_url
2121 (GUPNP_SERVICE_INFO (proxy));
2123 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2129 soup_message_headers_append (msg->request_headers,
2134 session = gupnp_context_get_session (context);
2136 soup_session_queue_message (session, msg, NULL, NULL);
2140 g_free (proxy->priv->sid);
2141 proxy->priv->sid = NULL;
2143 /* Reset sequence number */
2144 proxy->priv->seq = 0;
2147 /* Remove subscription timeout */
2148 if (proxy->priv->subscription_timeout_src) {
2149 g_source_destroy (proxy->priv->subscription_timeout_src);
2150 proxy->priv->subscription_timeout_src = NULL;
2155 * gupnp_service_proxy_set_subscribed:
2156 * @proxy: A #GUPnPServiceProxy
2157 * @subscribed: %TRUE to subscribe to this service
2159 * (Un)subscribes to this service.
2161 * Note that the relevant messages are not immediately sent but queued.
2162 * If you want to unsubcribe from this service because the application
2163 * is quitting, rely on automatic synchronised unsubscription on object
2164 * destruction instead.
2167 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2168 gboolean subscribed)
2170 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2172 if (proxy->priv->subscribed == subscribed)
2175 proxy->priv->subscribed = subscribed;
2180 unsubscribe (proxy);
2182 g_object_notify (G_OBJECT (proxy), "subscribed");
2186 * gupnp_service_proxy_get_subscribed:
2187 * @proxy: A #GUPnPServiceProxy
2189 * Returns if we are subscribed to this service.
2191 * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2194 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2196 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2198 return proxy->priv->subscribed;