Back to index

libindicator  12.10.0
indicator-service.c
Go to the documentation of this file.
00001 /*
00002 An object used to provide a simple interface for a service
00003 to query version and manage whether it's running.
00004 
00005 Copyright 2009 Canonical Ltd.
00006 
00007 Authors:
00008     Ted Gould <ted@canonical.com>
00009 
00010 This library is free software; you can redistribute it and/or
00011 modify it under the terms of the GNU General Public License
00012 version 3.0 as published by the Free Software Foundation.
00013 
00014 This library is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 GNU General Public License version 3.0 for more details.
00018 
00019 You should have received a copy of the GNU General Public
00020 License along with this library. If not, see
00021 <http://www.gnu.org/licenses/>.
00022 */
00023 
00024 #ifdef HAVE_CONFIG_H
00025 #include "config.h"
00026 #endif
00027 
00028 #include <gio/gio.h>
00029 
00030 #include "indicator-service.h"
00031 #include "gen-indicator-service.xml.h"
00032 #include "dbus-shared.h"
00033 
00034 static void unwatch_core (IndicatorService * service, const gchar * name);
00035 static void watchers_remove (gpointer value);
00036 static void bus_get_cb (GObject * object, GAsyncResult * res, gpointer user_data);
00037 static GVariant * bus_watch (IndicatorService * service, const gchar * sender);
00038 
00039 /* Private Stuff */
00050 typedef struct _IndicatorServicePrivate IndicatorServicePrivate;
00051 struct _IndicatorServicePrivate {
00052        gchar * name;
00053        GDBusConnection * bus;
00054        GCancellable * bus_cancel;
00055        guint timeout;
00056        guint timeout_length;
00057        GHashTable * watchers;
00058        guint this_service_version;
00059        guint dbus_registration;
00060        gboolean replace_mode;
00061 };
00062 
00063 /* Signals Stuff */
00064 enum {
00065        SHUTDOWN,
00066        LAST_SIGNAL
00067 };
00068 
00069 static guint signals[LAST_SIGNAL] = { 0 };
00070 
00071 /* Properties */
00072 /* Enum for the properties so that they can be quickly
00073    found and looked up. */
00074 enum {
00075        PROP_0,
00076        PROP_NAME,
00077        PROP_VERSION
00078 };
00079 
00080 /* The strings so that they can be slowly looked up. */
00081 #define PROP_NAME_S                    "name"
00082 #define PROP_VERSION_S                 "version"
00083 
00084 /* GObject Stuff */
00085 #define INDICATOR_SERVICE_GET_PRIVATE(o) \
00086                      (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_SERVICE_TYPE, IndicatorServicePrivate))
00087 
00088 static void indicator_service_class_init (IndicatorServiceClass *klass);
00089 static void indicator_service_init       (IndicatorService *self);
00090 static void indicator_service_dispose    (GObject *object);
00091 static void indicator_service_finalize   (GObject *object);
00092 
00093 /* Other prototypes */
00094 static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
00095 static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
00096 static void try_and_get_name (IndicatorService * service);
00097 static void bus_method_call (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * method, GVariant * params, GDBusMethodInvocation * invocation, gpointer user_data); 
00098 
00099 /* GDBus Stuff */
00100 static GDBusNodeInfo *            node_info = NULL;
00101 static GDBusInterfaceInfo *       interface_info = NULL;
00102 static GDBusInterfaceVTable       interface_table = {
00103        method_call:  bus_method_call,
00104        get_property: NULL, /* No properties */
00105        set_property: NULL  /* No properties */
00106 };
00107 
00108 /* THE define */
00109 G_DEFINE_TYPE (IndicatorService, indicator_service, G_TYPE_OBJECT);
00110 
00111 static void
00112 indicator_service_class_init (IndicatorServiceClass *klass)
00113 {
00114        GObjectClass *object_class = G_OBJECT_CLASS (klass);
00115 
00116        g_type_class_add_private (klass, sizeof (IndicatorServicePrivate));
00117 
00118        object_class->dispose = indicator_service_dispose;
00119        object_class->finalize = indicator_service_finalize;
00120 
00121        /* Property funcs */
00122        object_class->set_property = set_property;
00123        object_class->get_property = get_property;
00124 
00125        /* Properties */
00126        g_object_class_install_property(object_class, PROP_NAME,
00127                                        g_param_spec_string(PROP_NAME_S,
00128                                                            "The DBus name for this service",
00129                                                            "This is the name that should be used on DBus for this service.",
00130                                                            NULL,
00131                                                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
00132        g_object_class_install_property(object_class, PROP_VERSION,
00133                                        g_param_spec_uint(PROP_VERSION_S,
00134                                                          "The version of the service that we're implementing.",
00135                                                          "A number to represent the version of the other APIs the service provides.  This should match across the manager and the service",
00136                                                          0, G_MAXUINT, 0,
00137                                                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
00138 
00139        /* Signals */
00140 
00148        signals[SHUTDOWN] = g_signal_new (INDICATOR_SERVICE_SIGNAL_SHUTDOWN,
00149                                          G_TYPE_FROM_CLASS(klass),
00150                                          G_SIGNAL_RUN_LAST,
00151                                          G_STRUCT_OFFSET (IndicatorServiceClass, shutdown),
00152                                          NULL, NULL,
00153                                          g_cclosure_marshal_VOID__VOID,
00154                                          G_TYPE_NONE, 0, G_TYPE_NONE);
00155 
00156        /* Setting up the DBus interfaces */
00157        if (node_info == NULL) {
00158               GError * error = NULL;
00159 
00160               node_info = g_dbus_node_info_new_for_xml(_indicator_service, &error);
00161               if (error != NULL) {
00162                      g_error("Unable to parse Indicator Service Interface description: %s", error->message);
00163                      g_error_free(error);
00164               }
00165        }
00166 
00167        if (interface_info == NULL) {
00168               interface_info = g_dbus_node_info_lookup_interface(node_info, INDICATOR_SERVICE_INTERFACE);
00169 
00170               if (interface_info == NULL) {
00171                      g_error("Unable to find interface '" INDICATOR_SERVICE_INTERFACE "'");
00172               }
00173        }
00174 
00175        return;
00176 }
00177 
00178 /* This function builds the variables, sets up the dbus
00179    proxy and registers the object on dbus.  Importantly,
00180    it does not request a name as we don't know what name
00181    we have yet. */
00182 static void
00183 indicator_service_init (IndicatorService *self)
00184 {
00185        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(self);
00186 
00187        /* Get the private variables in a decent state */
00188        priv->name = NULL;
00189        priv->timeout = 0;
00190        priv->watchers = NULL;
00191        priv->bus = NULL;
00192        priv->bus_cancel = NULL;
00193        priv->this_service_version = 0;
00194        priv->timeout_length = 500;
00195        priv->dbus_registration = 0;
00196        priv->replace_mode = FALSE;
00197 
00198        const gchar * timeoutenv = g_getenv("INDICATOR_SERVICE_SHUTDOWN_TIMEOUT");
00199        if (timeoutenv != NULL) {
00200               gdouble newtimeout = g_strtod(timeoutenv, NULL);
00201               if (newtimeout >= 1.0f) {
00202                      priv->timeout_length = newtimeout;
00203                      g_debug("Setting shutdown timeout to: %u", priv->timeout_length);
00204               }
00205        }
00206 
00207        const gchar * replaceenv = g_getenv("INDICATOR_SERVICE_REPLACE_MODE");
00208        if (replaceenv != NULL) {
00209               priv->replace_mode = TRUE;
00210               g_debug("Putting into replace mode");
00211        }
00212 
00213        /* NOTE: We're using g_free here because that's what needs to
00214           happen and we're watchers_remove as well to clean up the dbus
00215           watches we've setup. */
00216        priv->watchers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, watchers_remove);
00217 
00218        priv->bus_cancel = g_cancellable_new();
00219        g_bus_get(G_BUS_TYPE_SESSION,
00220                  priv->bus_cancel,
00221                  bus_get_cb,
00222                  self);
00223 
00224        return;
00225 }
00226 
00227 /* Unrefcounting the proxies and making sure that our
00228    timeout doesn't come to haunt us. */
00229 static void
00230 indicator_service_dispose (GObject *object)
00231 {
00232        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(object);
00233 
00234        if (priv->watchers != NULL) {
00235               g_hash_table_destroy(priv->watchers);
00236               priv->watchers = NULL;
00237        }
00238 
00239        if (priv->timeout != 0) {
00240               g_source_remove(priv->timeout);
00241               priv->timeout = 0;
00242        }
00243 
00244        if (priv->dbus_registration != 0) {
00245               g_dbus_connection_unregister_object(priv->bus, priv->dbus_registration);
00246               /* Don't care if it fails, there's nothing we can do */
00247               priv->dbus_registration = 0;
00248        }
00249 
00250        if (priv->bus != NULL) {
00251               g_object_unref(priv->bus);
00252               priv->bus = NULL;
00253        }
00254 
00255        if (priv->bus_cancel != NULL) {
00256               g_cancellable_cancel(priv->bus_cancel);
00257               g_object_unref(priv->bus_cancel);
00258               priv->bus_cancel = NULL;
00259        }
00260 
00261        G_OBJECT_CLASS (indicator_service_parent_class)->dispose (object);
00262        return;
00263 }
00264 
00265 /* Freeing the name we're looking for and all of the
00266    information on the watchers we're tracking. */
00267 static void
00268 indicator_service_finalize (GObject *object)
00269 {
00270        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(object);
00271 
00272        if (priv->name != NULL) {
00273               g_free(priv->name);
00274        }
00275 
00276        if (priv->watchers != NULL) {
00277               g_hash_table_destroy(priv->watchers);
00278               priv->watchers = NULL;
00279        }
00280 
00281        G_OBJECT_CLASS (indicator_service_parent_class)->finalize (object);
00282        return;
00283 }
00284 
00285 /* Either copies a string for the name or it just grabs
00286    the value of the version. */
00287 static void
00288 set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
00289 {
00290        IndicatorService * self = INDICATOR_SERVICE(object);
00291        g_return_if_fail(self != NULL);
00292 
00293        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(self);
00294        g_return_if_fail(priv != NULL);
00295 
00296        switch (prop_id) {
00297        /* *********************** */
00298        case PROP_NAME:
00299               if (G_VALUE_HOLDS_STRING(value)) {
00300                      if (priv->name != NULL) {
00301                             g_error("Name can not be set twice!");
00302                             return;
00303                      }
00304                      priv->name = g_value_dup_string(value);
00305                      try_and_get_name(self);
00306               } else {
00307                      g_warning("Name property requires a string value.");
00308               }
00309               break;
00310        /* *********************** */
00311        case PROP_VERSION:
00312               priv->this_service_version = g_value_get_uint(value);
00313               break;
00314        /* *********************** */
00315        default:
00316               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
00317               break;
00318        }
00319 
00320        return;
00321 }
00322 
00323 /* Copies out the name into a value or the version number.
00324    Probably this is the least useful code in this file. */
00325 static void
00326 get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
00327 {
00328        IndicatorService * self = INDICATOR_SERVICE(object);
00329        g_return_if_fail(self != NULL);
00330 
00331        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(self);
00332        g_return_if_fail(priv != NULL);
00333 
00334        switch (prop_id) {
00335        /* *********************** */
00336        case PROP_NAME:
00337               if (G_VALUE_HOLDS_STRING(value)) {
00338                      g_value_set_string(value, priv->name);
00339               } else {
00340                      g_warning("Name property requires a string value.");
00341               }
00342               break;
00343        /* *********************** */
00344        case PROP_VERSION:
00345               g_value_set_uint(value, priv->this_service_version);
00346               break;
00347        /* *********************** */
00348        default:
00349               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
00350               break;
00351        }
00352 
00353        return;
00354 }
00355 
00356 /* Callback for getting our connection to DBus */
00357 static void
00358 bus_get_cb (GObject * object, GAsyncResult * res, gpointer user_data)
00359 {
00360        GError * error = NULL;
00361        GDBusConnection * connection = g_bus_get_finish(res, &error);
00362 
00363        if (error != NULL) {
00364               g_error("OMG! Unable to get a connection to DBus: %s", error->message);
00365               g_error_free(error);
00366               return;
00367        }
00368 
00369        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(user_data);
00370 
00371        g_warn_if_fail(priv->bus == NULL);
00372        priv->bus = connection;
00373 
00374        if (priv->bus_cancel != NULL) {
00375               g_object_unref(priv->bus_cancel);
00376               priv->bus_cancel = NULL;
00377        }
00378 
00379        /* Now register our object on our new connection */
00380        priv->dbus_registration = g_dbus_connection_register_object(priv->bus,
00381                                                                    INDICATOR_SERVICE_OBJECT,
00382                                                                    interface_info,
00383                                                                    &interface_table,
00384                                                                    user_data,
00385                                                                    NULL,
00386                                                                    &error);
00387        if (error != NULL) {
00388               g_error("Unable to register the object to DBus: %s", error->message);
00389               g_error_free(error);
00390               return;
00391        }
00392 
00393        return;
00394 }
00395 
00396 /* A method has been called from our dbus inteface.  Figure out what it
00397    is and dispatch it. */
00398 static void
00399 bus_method_call (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * method, GVariant * params, GDBusMethodInvocation * invocation, gpointer user_data) 
00400 {
00401        IndicatorService * service = INDICATOR_SERVICE(user_data);
00402        GVariant * retval = NULL;
00403 
00404        if (g_strcmp0(method, "Watch") == 0) {
00405               retval = bus_watch(service, sender);
00406        } else if (g_strcmp0(method, "UnWatch") == 0) {
00407               unwatch_core(service, sender);
00408        } else if (g_strcmp0(method, "Shutdown") == 0) {
00409               g_signal_emit(G_OBJECT(service), signals[SHUTDOWN], 0, TRUE);
00410        } else {
00411               g_warning("Calling method '%s' on the indicator service and it's unknown", method);
00412        }
00413 
00414        g_dbus_method_invocation_return_value(invocation, retval);
00415        return;
00416 }
00417 
00418 /* A function to remove the signals on a proxy before we destroy
00419    it because in this case we've stopped caring. */
00420 static void
00421 watchers_remove (gpointer value)
00422 {
00423        g_bus_unwatch_name(GPOINTER_TO_UINT(value));
00424        return;
00425 }
00426 
00427 /* This is the function that gets executed if we timeout
00428    because there are no watchers.  We sent the shutdown
00429    signal and hope someone does something sane with it. */
00430 static gboolean
00431 timeout_no_watchers (gpointer data)
00432 {
00433        g_warning("No watchers, service timing out.");
00434        if (g_getenv("INDICATOR_ALLOW_NO_WATCHERS") == NULL) {
00435               g_signal_emit(G_OBJECT(data), signals[SHUTDOWN], 0, TRUE);
00436        } else {
00437               g_warning("\tblocked by environment variable.");
00438        }
00439        return FALSE;
00440 }
00441 
00442 /* Callback saying that the name we were looking for has been
00443    found and we've got it.  Now start the timer to see if anyone
00444    cares about us. */
00445 static void
00446 try_and_get_name_acquired_cb (GDBusConnection * connection, const gchar * name, gpointer user_data)
00447 {
00448        g_return_if_fail(connection != NULL);
00449        g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
00450 
00451        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(user_data);
00452 
00453        /* Check to see if we already had a timer, if so we want to
00454           extend it a bit. */
00455        if (priv->timeout != 0) {
00456               g_source_remove(priv->timeout);
00457               priv->timeout = 0;
00458        }
00459 
00460        /* Allow some extra time at start up as things can be in high
00461           contention then. */
00462        priv->timeout = g_timeout_add(priv->timeout_length * 2, timeout_no_watchers, user_data);
00463 
00464        return;
00465 }
00466 
00467 /* Callback saying that we didn't get the name, so we need to
00468    shutdown this service. */
00469 static void
00470 try_and_get_name_lost_cb (GDBusConnection * connection, const gchar * name, gpointer user_data)
00471 {
00472        g_return_if_fail(connection != NULL);
00473        g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
00474 
00475        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(user_data);
00476 
00477        if (!priv->replace_mode) {
00478               g_warning("Name request failed.");
00479               g_signal_emit(G_OBJECT(user_data), signals[SHUTDOWN], 0, TRUE);
00480        } else {
00481               /* If we're in replace mode we can be a little more trickey
00482                  here.  We're going to tell the other guy to shutdown and hope
00483                  that we get the name. */
00484               GDBusMessage * message = NULL;
00485               message = g_dbus_message_new_method_call(name,
00486                                                        INDICATOR_SERVICE_OBJECT,
00487                                                        INDICATOR_SERVICE_INTERFACE,
00488                                                        "Shutdown");
00489 
00490               g_dbus_connection_send_message(connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL);
00491               g_object_unref(message);
00492 
00493               /* Check to see if we need to clean up a timeout */
00494               if (priv->timeout != 0) {
00495                      g_source_remove(priv->timeout);
00496                      priv->timeout = 0;
00497               }
00498 
00499               /* Set a timeout for no watchers if we can't get the name */
00500               priv->timeout = g_timeout_add(priv->timeout_length * 4, timeout_no_watchers, user_data);
00501        }
00502 
00503        return;
00504 }
00505 
00506 /* This function sets up the request for the name on dbus. */
00507 static void
00508 try_and_get_name (IndicatorService * service)
00509 {
00510        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(service);
00511        g_return_if_fail(priv->name != NULL);
00512 
00513        g_bus_own_name(G_BUS_TYPE_SESSION,
00514                       priv->name,
00515                       G_BUS_NAME_OWNER_FLAGS_NONE,
00516                       NULL, /* bus acquired */
00517                       try_and_get_name_acquired_cb, /* name acquired */
00518                       try_and_get_name_lost_cb, /* name lost */
00519                       service,
00520                       NULL); /* user data destroy */
00521 
00522        return;
00523 }
00524 
00525 /* When the watcher vanishes we don't really care about it
00526    anymore. */
00527 static void
00528 watcher_vanished_cb (GDBusConnection * connection, const gchar * name, gpointer user_data)
00529 {
00530        g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
00531        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(user_data);
00532 
00533        gpointer finddata = g_hash_table_lookup(priv->watchers, name);
00534        if (finddata != NULL) {
00535               unwatch_core(INDICATOR_SERVICE(user_data), name);
00536        } else {
00537               g_warning("Odd, we were watching for '%s' and it disappeard, but then it wasn't in the hashtable.", name);
00538        }
00539 
00540        return;
00541 }
00542 
00543 /* Here is the function that gets called by the dbus
00544    interface "Watch" function.  It is an async function so
00545    that we can get the sender and store that information.  We
00546    put them in a list and reset the timeout. */
00547 static GVariant *
00548 bus_watch (IndicatorService * service, const gchar * sender)
00549 {
00550        g_return_val_if_fail(INDICATOR_IS_SERVICE(service), NULL);
00551        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(service);
00552        
00553        if (GPOINTER_TO_UINT(g_hash_table_lookup(priv->watchers, sender)) == 0) {
00554               guint watch = g_bus_watch_name_on_connection(priv->bus,
00555                                                            sender,
00556                                                            G_BUS_NAME_WATCHER_FLAGS_NONE,
00557                                                            NULL, /* appeared, we dont' care, should have already happened. */
00558                                                            watcher_vanished_cb,
00559                                                            service,
00560                                                            NULL);
00561 
00562               if (watch != 0) {
00563                      g_hash_table_insert(priv->watchers, g_strdup(sender), GUINT_TO_POINTER(watch));
00564               } else {
00565                      g_warning("Unable watch for '%s'", sender);
00566               }
00567        }
00568 
00569        if (priv->timeout != 0) {
00570               g_source_remove(priv->timeout);
00571               priv->timeout = 0;
00572        }
00573 
00574        return g_variant_new("(uu)", INDICATOR_SERVICE_VERSION, priv->this_service_version);
00575 }
00576 
00577 /* Performs the core of loosing a watcher; it removes them
00578    from the list of watchers.  If there are none left, it then
00579    starts the timer for the shutdown signal. */
00580 static void
00581 unwatch_core (IndicatorService * service, const gchar * name)
00582 {
00583        g_return_if_fail(name != NULL);
00584        g_return_if_fail(INDICATOR_IS_SERVICE(service));
00585 
00586        IndicatorServicePrivate * priv = INDICATOR_SERVICE_GET_PRIVATE(service);
00587 
00588        /* Remove us from the watcher list here */
00589        gpointer watcher_item = g_hash_table_lookup(priv->watchers, name);
00590        if (watcher_item != NULL) {
00591               gchar * safe_name = g_strdup(name);
00592               g_hash_table_remove(priv->watchers, safe_name);
00593               g_free(safe_name);
00594        } else {
00595               /* Odd that we couldn't find the person, but, eh */
00596               g_warning("Unable to find watcher who is unwatching: %s", name);
00597        }
00598 
00599        /* If we're out of watchers set the timeout for shutdown */
00600        if (g_hash_table_size(priv->watchers) == 0) {
00601               if (priv->timeout != 0) {
00602                      /* This should never really happen, but let's ensure that
00603                         bad things don't happen if it does. */
00604                      g_warning("No watchers timeout set twice.  Resolving, but odd.");
00605                      g_source_remove(priv->timeout);
00606                      priv->timeout = 0;
00607               }
00608               /* If we don't get a new watcher quickly, we'll shutdown. */
00609               priv->timeout = g_timeout_add(priv->timeout_length, timeout_no_watchers, service);
00610        }
00611 
00612        return;
00613 }
00614 
00615 /* API */
00616 
00629 IndicatorService *
00630 indicator_service_new (gchar * name)
00631 {
00632        GObject * obj = g_object_new(INDICATOR_SERVICE_TYPE,
00633                                     PROP_NAME_S, name,
00634                                     NULL);
00635 
00636        return INDICATOR_SERVICE(obj);
00637 }
00638 
00653 IndicatorService *
00654 indicator_service_new_version (gchar * name, guint version)
00655 {
00656        GObject * obj = g_object_new(INDICATOR_SERVICE_TYPE,
00657                                     PROP_NAME_S, name,
00658                                     PROP_VERSION_S, version,
00659                                     NULL);
00660 
00661        return INDICATOR_SERVICE(obj);
00662 }