f6126e5aeab2bd4678aedcde33fbd52fdf521c7a
[platform/upstream/glib.git] / gio / gdbusnamewatching.c
1 /* GDBus - GLib D-Bus Library
2  *
3  * Copyright (C) 2008-2009 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Author: David Zeuthen <davidz@redhat.com>
21  */
22
23 #include "config.h"
24
25 #include <stdlib.h>
26
27 #include <glib/gi18n.h>
28
29 #include "gdbusutils.h"
30 #include "gdbusnamewatching.h"
31 #include "gdbuserror.h"
32 #include "gdbusprivate.h"
33 #include "gdbusconnection.h"
34
35 /**
36  * SECTION:gdbusnamewatching
37  * @title: Watching Bus Names
38  * @short_description: Simple API for watching bus names
39  * @include: gio/gio.h
40  *
41  * Convenience API for watching bus names.
42  *
43  * <example id="gdbus-watching-names"><title>Simple application watching a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/tests/gdbus-example-watch-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
44  */
45
46 G_LOCK_DEFINE_STATIC (lock);
47
48 /* ---------------------------------------------------------------------------------------------------- */
49
50 typedef enum
51 {
52   PREVIOUS_CALL_NONE = 0,
53   PREVIOUS_CALL_APPEARED,
54   PREVIOUS_CALL_VANISHED,
55 } PreviousCall;
56
57 typedef struct
58 {
59   volatile gint             ref_count;
60   guint                     id;
61   gchar                    *name;
62   GBusNameWatcherFlags      flags;
63   gchar                    *name_owner;
64   GBusNameAppearedCallback  name_appeared_handler;
65   GBusNameVanishedCallback  name_vanished_handler;
66   gpointer                  user_data;
67   GDestroyNotify            user_data_free_func;
68   GMainContext             *main_context;
69
70   GDBusConnection          *connection;
71   gulong                    disconnected_signal_handler_id;
72   guint                     name_owner_changed_subscription_id;
73
74   PreviousCall              previous_call;
75
76   gboolean                  cancelled;
77   gboolean                  initialized;
78 } Client;
79
80 static guint next_global_id = 1;
81 static GHashTable *map_id_to_client = NULL;
82
83 static Client *
84 client_ref (Client *client)
85 {
86   g_atomic_int_inc (&client->ref_count);
87   return client;
88 }
89
90 static void
91 client_unref (Client *client)
92 {
93   if (g_atomic_int_dec_and_test (&client->ref_count))
94     {
95       if (client->connection != NULL)
96         {
97           if (client->name_owner_changed_subscription_id > 0)
98             g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
99           if (client->disconnected_signal_handler_id > 0)
100             g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
101           g_object_unref (client->connection);
102         }
103       g_free (client->name);
104       g_free (client->name_owner);
105       if (client->main_context != NULL)
106         g_main_context_unref (client->main_context);
107       if (client->user_data_free_func != NULL)
108         client->user_data_free_func (client->user_data);
109       g_free (client);
110     }
111 }
112
113 /* ---------------------------------------------------------------------------------------------------- */
114
115 typedef enum
116 {
117   CALL_TYPE_NAME_APPEARED,
118   CALL_TYPE_NAME_VANISHED
119 } CallType;
120
121 typedef struct
122 {
123   Client *client;
124
125   /* keep this separate because client->connection may
126    * be set to NULL after scheduling the call
127    */
128   GDBusConnection *connection;
129
130   /* ditto */
131   gchar *name_owner;
132
133   CallType call_type;
134 } CallHandlerData;
135
136 static void
137 call_handler_data_free (CallHandlerData *data)
138 {
139   if (data->connection != NULL)
140     g_object_unref (data->connection);
141   g_free (data->name_owner);
142   client_unref (data->client);
143   g_free (data);
144 }
145
146 static void
147 actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type)
148 {
149   switch (call_type)
150     {
151     case CALL_TYPE_NAME_APPEARED:
152       if (client->name_appeared_handler != NULL)
153         {
154           client->name_appeared_handler (connection,
155                                          client->name,
156                                          name_owner,
157                                          client->user_data);
158         }
159       break;
160
161     case CALL_TYPE_NAME_VANISHED:
162       if (client->name_vanished_handler != NULL)
163         {
164           client->name_vanished_handler (connection,
165                                          client->name,
166                                          client->user_data);
167         }
168       break;
169
170     default:
171       g_assert_not_reached ();
172       break;
173     }
174 }
175
176 static gboolean
177 call_in_idle_cb (gpointer _data)
178 {
179   CallHandlerData *data = _data;
180   actually_do_call (data->client, data->connection, data->name_owner, data->call_type);
181   return FALSE;
182 }
183
184 static void
185 schedule_call_in_idle (Client *client, CallType call_type)
186 {
187   CallHandlerData *data;
188   GSource *idle_source;
189
190   data = g_new0 (CallHandlerData, 1);
191   data->client = client_ref (client);
192   data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
193   data->name_owner = g_strdup (client->name_owner);
194   data->call_type = call_type;
195
196   idle_source = g_idle_source_new ();
197   g_source_set_priority (idle_source, G_PRIORITY_HIGH);
198   g_source_set_callback (idle_source,
199                          call_in_idle_cb,
200                          data,
201                          (GDestroyNotify) call_handler_data_free);
202   g_source_attach (idle_source, client->main_context);
203   g_source_unref (idle_source);
204 }
205
206 static void
207 do_call (Client *client, CallType call_type)
208 {
209   /* only schedule in idle if we're not in the right thread */
210   if (g_main_context_get_thread_default () != client->main_context)
211     schedule_call_in_idle (client, call_type);
212   else
213     actually_do_call (client, client->connection, client->name_owner, call_type);
214 }
215
216 static void
217 call_appeared_handler (Client *client)
218 {
219   if (client->previous_call != PREVIOUS_CALL_APPEARED)
220     {
221       client->previous_call = PREVIOUS_CALL_APPEARED;
222       if (!client->cancelled && client->name_appeared_handler != NULL)
223         {
224           do_call (client, CALL_TYPE_NAME_APPEARED);
225         }
226     }
227 }
228
229 static void
230 call_vanished_handler (Client  *client,
231                        gboolean ignore_cancelled)
232 {
233   if (client->previous_call != PREVIOUS_CALL_VANISHED)
234     {
235       client->previous_call = PREVIOUS_CALL_VANISHED;
236       if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL)
237         {
238           do_call (client, CALL_TYPE_NAME_VANISHED);
239         }
240     }
241 }
242
243 /* ---------------------------------------------------------------------------------------------------- */
244
245 static void
246 on_connection_disconnected (GDBusConnection *connection,
247                             gboolean         remote_peer_vanished,
248                             GError          *error,
249                             gpointer         user_data)
250 {
251   Client *client = user_data;
252
253   if (client->name_owner_changed_subscription_id > 0)
254     g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
255   if (client->disconnected_signal_handler_id > 0)
256     g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
257   g_object_unref (client->connection);
258   client->disconnected_signal_handler_id = 0;
259   client->name_owner_changed_subscription_id = 0;
260   client->connection = NULL;
261
262   call_vanished_handler (client, FALSE);
263 }
264
265 /* ---------------------------------------------------------------------------------------------------- */
266
267 static void
268 on_name_owner_changed (GDBusConnection *connection,
269                        const gchar      *sender_name,
270                        const gchar      *object_path,
271                        const gchar      *interface_name,
272                        const gchar      *signal_name,
273                        GVariant         *parameters,
274                        gpointer          user_data)
275 {
276   Client *client = user_data;
277   const gchar *name;
278   const gchar *old_owner;
279   const gchar *new_owner;
280
281   if (!client->initialized)
282     goto out;
283
284   if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
285       g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
286       g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
287     goto out;
288
289   g_variant_get (parameters,
290                  "(sss)",
291                  &name,
292                  &old_owner,
293                  &new_owner);
294
295   /* we only care about a specific name */
296   if (g_strcmp0 (name, client->name) != 0)
297     goto out;
298
299   if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
300     {
301       g_free (client->name_owner);
302       client->name_owner = NULL;
303       call_vanished_handler (client, FALSE);
304     }
305
306   if (new_owner != NULL && strlen (new_owner) > 0)
307     {
308       g_warn_if_fail (client->name_owner == NULL);
309       g_free (client->name_owner);
310       client->name_owner = g_strdup (new_owner);
311       call_appeared_handler (client);
312     }
313
314  out:
315   ;
316 }
317
318 /* ---------------------------------------------------------------------------------------------------- */
319
320 static void
321 get_name_owner_cb (GObject      *source_object,
322                    GAsyncResult *res,
323                    gpointer      user_data)
324 {
325   Client *client = user_data;
326   GVariant *result;
327   const char *name_owner;
328
329   name_owner = NULL;
330   result = NULL;
331
332   result = g_dbus_connection_invoke_method_finish (client->connection,
333                                                    res,
334                                                    NULL);
335   if (result != NULL)
336     {
337       g_variant_get (result, "(s)", &name_owner);
338     }
339
340   if (name_owner != NULL)
341     {
342       g_warn_if_fail (client->name_owner == NULL);
343       client->name_owner = g_strdup (name_owner);
344       call_appeared_handler (client);
345     }
346   else
347     {
348       call_vanished_handler (client, FALSE);
349     }
350
351   client->initialized = TRUE;
352
353   if (result != NULL)
354     g_variant_unref (result);
355   client_unref (client);
356 }
357
358 /* ---------------------------------------------------------------------------------------------------- */
359
360 static void
361 invoke_get_name_owner (Client *client)
362 {
363   g_dbus_connection_invoke_method (client->connection,
364                                    "org.freedesktop.DBus",  /* bus name */
365                                    "/org/freedesktop/DBus", /* object path */
366                                    "org.freedesktop.DBus",  /* interface name */
367                                    "GetNameOwner",          /* method name */
368                                    g_variant_new ("(s)", client->name),
369                                    G_DBUS_INVOKE_METHOD_FLAGS_NONE,
370                                    -1,
371                                    NULL,
372                                    (GAsyncReadyCallback) get_name_owner_cb,
373                                    client_ref (client));
374 }
375
376 /* ---------------------------------------------------------------------------------------------------- */
377
378 static void
379 start_service_by_name_cb (GObject      *source_object,
380                           GAsyncResult *res,
381                           gpointer      user_data)
382 {
383   Client *client = user_data;
384   GVariant *result;
385
386   result = NULL;
387
388   result = g_dbus_connection_invoke_method_finish (client->connection,
389                                                    res,
390                                                    NULL);
391   if (result != NULL)
392     {
393       guint32 start_service_result;
394       g_variant_get (result, "(u)", &start_service_result);
395
396       if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */
397         {
398           invoke_get_name_owner (client);
399         }
400       else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */
401         {
402           invoke_get_name_owner (client);
403         }
404       else
405         {
406           g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result);
407           call_vanished_handler (client, FALSE);
408           client->initialized = TRUE;
409         }
410     }
411   else
412     {
413       /* Errors are not unexpected; the bus will reply e.g.
414        *
415        *   org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2
416        *   was not provided by any .service files
417        *
418        * so just report vanished.
419        */
420       call_vanished_handler (client, FALSE);
421       client->initialized = TRUE;
422     }
423
424   if (result != NULL)
425     g_variant_unref (result);
426   client_unref (client);
427 }
428
429 /* ---------------------------------------------------------------------------------------------------- */
430
431 static void
432 has_connection (Client *client)
433 {
434   /* listen for disconnection */
435   client->disconnected_signal_handler_id = g_signal_connect (client->connection,
436                                                              "closed",
437                                                              G_CALLBACK (on_connection_disconnected),
438                                                              client);
439
440   /* start listening to NameOwnerChanged messages immediately */
441   client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection,
442                                                                                    "org.freedesktop.DBus",  /* name */
443                                                                                    "org.freedesktop.DBus",  /* if */
444                                                                                    "NameOwnerChanged",      /* signal */
445                                                                                    "/org/freedesktop/DBus", /* path */
446                                                                                    client->name,
447                                                                                    on_name_owner_changed,
448                                                                                    client,
449                                                                                    NULL);
450
451   if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
452     {
453       g_dbus_connection_invoke_method (client->connection,
454                                        "org.freedesktop.DBus",  /* bus name */
455                                        "/org/freedesktop/DBus", /* object path */
456                                        "org.freedesktop.DBus",  /* interface name */
457                                        "StartServiceByName",    /* method name */
458                                        g_variant_new ("(su)", client->name, 0),
459                                        G_DBUS_INVOKE_METHOD_FLAGS_NONE,
460                                        -1,
461                                        NULL,
462                                        (GAsyncReadyCallback) start_service_by_name_cb,
463                                        client_ref (client));
464     }
465   else
466     {
467       /* check owner */
468       invoke_get_name_owner (client);
469     }
470 }
471
472
473 static void
474 connection_get_cb (GObject      *source_object,
475                    GAsyncResult *res,
476                    gpointer      user_data)
477 {
478   Client *client = user_data;
479
480   client->connection = g_bus_get_finish (res, NULL);
481   if (client->connection == NULL)
482     {
483       call_vanished_handler (client, FALSE);
484       goto out;
485     }
486
487   has_connection (client);
488
489  out:
490   client_unref (client);
491 }
492
493 /* ---------------------------------------------------------------------------------------------------- */
494
495 /**
496  * g_bus_watch_name:
497  * @bus_type: The type of bus to watch a name on.
498  * @name: The name (well-known or unique) to watch.
499  * @flags: Flags from the #GBusNameWatcherFlags enumeration.
500  * @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL.
501  * @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL.
502  * @user_data: User data to pass to handlers.
503  * @user_data_free_func: Function for freeing @user_data or %NULL.
504  *
505  * Starts watching @name on the bus specified by @bus_type and calls
506  * @name_appeared_handler and @name_vanished_handler when the name is
507  * known to have a owner respectively known to lose its
508  * owner. Callbacks will be invoked in the <link
509  * linkend="g-main-context-push-thread-default">thread-default main
510  * loop</link> of the thread you are calling this function from.
511  *
512  * You are guaranteed that one of the handlers will be invoked after
513  * calling this function. When you are done watching the name, just
514  * call g_bus_unwatch_name() with the watcher id this function
515  * returns.
516  *
517  * If the name vanishes or appears (for example the application owning
518  * the name could restart), the handlers are also invoked. If the
519  * #GDBusConnection that is used for watching the name disconnects, then
520  * @name_vanished_handler is invoked since it is no longer
521  * possible to access the name.
522  *
523  * Another guarantee is that invocations of @name_appeared_handler
524  * and @name_vanished_handler are guaranteed to alternate; that
525  * is, if @name_appeared_handler is invoked then you are
526  * guaranteed that the next time one of the handlers is invoked, it
527  * will be @name_vanished_handler. The reverse is also true.
528  *
529  * This behavior makes it very simple to write applications that wants
530  * to take action when a certain name exists, see <xref
531  * linkend="gdbus-watching-names"/>. Basically, the application
532  * should create object proxies in @name_appeared_handler and destroy
533  * them again (if any) in @name_vanished_handler.
534  *
535  * Returns: An identifier (never 0) that an be used with
536  * g_bus_unwatch_name() to stop watching the name.
537  *
538  * Since: 2.26
539  */
540 guint
541 g_bus_watch_name (GBusType                  bus_type,
542                   const gchar              *name,
543                   GBusNameWatcherFlags      flags,
544                   GBusNameAppearedCallback  name_appeared_handler,
545                   GBusNameVanishedCallback  name_vanished_handler,
546                   gpointer                  user_data,
547                   GDestroyNotify            user_data_free_func)
548 {
549   Client *client;
550
551   g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
552   g_return_val_if_fail (g_dbus_is_name (name), 0);
553
554   G_LOCK (lock);
555
556   client = g_new0 (Client, 1);
557   client->ref_count = 1;
558   client->id = next_global_id++; /* TODO: uh oh, handle overflow */
559   client->name = g_strdup (name);
560   client->flags = flags;
561   client->name_appeared_handler = name_appeared_handler;
562   client->name_vanished_handler = name_vanished_handler;
563   client->user_data = user_data;
564   client->user_data_free_func = user_data_free_func;
565   client->main_context = g_main_context_get_thread_default ();
566   if (client->main_context != NULL)
567     g_main_context_ref (client->main_context);
568
569   if (map_id_to_client == NULL)
570     {
571       map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
572     }
573   g_hash_table_insert (map_id_to_client,
574                        GUINT_TO_POINTER (client->id),
575                        client);
576
577   g_bus_get (bus_type,
578              NULL,
579              connection_get_cb,
580              client_ref (client));
581
582   G_UNLOCK (lock);
583
584   return client->id;
585 }
586
587 /**
588  * g_bus_unwatch_name:
589  * @watcher_id: An identifier obtained from g_bus_watch_name()
590  *
591  * Stops watching a name.
592  *
593  * Since: 2.26
594  */
595 void
596 g_bus_unwatch_name (guint watcher_id)
597 {
598   Client *client;
599
600   g_return_if_fail (watcher_id > 0);
601
602   client = NULL;
603
604   G_LOCK (lock);
605   if (watcher_id == 0 ||
606       map_id_to_client == NULL ||
607       (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
608     {
609       g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id);
610       goto out;
611     }
612
613   client->cancelled = TRUE;
614   g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
615
616  out:
617   G_UNLOCK (lock);
618
619   /* do callback without holding lock */
620   if (client != NULL)
621     {
622       client_unref (client);
623     }
624 }