/* * e-dbus-server.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * */ /** * SECTION: e-dbus-server * @include: libebackend/libebackend.h * @short_description: An abstract base class for a D-Bus server **/ #include "e-dbus-server.h" #include #ifdef G_OS_UNIX #include #endif #include #include #include #include #define E_DBUS_SERVER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_DBUS_SERVER, EDBusServerPrivate)) #define INACTIVITY_TIMEOUT 10 /* seconds */ struct _EDBusServerPrivate { GMainLoop *main_loop; guint bus_owner_id; guint hang_up_id; guint terminate_id; guint inactivity_timeout_id; guint use_count; gboolean wait_for_client; EDBusServerExitCode exit_code; }; enum { BUS_ACQUIRED, BUS_NAME_ACQUIRED, BUS_NAME_LOST, RUN_SERVER, QUIT_SERVER, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static GHashTable *directories_loaded; G_LOCK_DEFINE_STATIC (directories_loaded); G_DEFINE_ABSTRACT_TYPE_WITH_CODE ( EDBusServer, e_dbus_server, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) static void dbus_server_bus_acquired_cb (GDBusConnection *connection, const gchar *bus_name, EDBusServer *server) { g_signal_emit (server, signals[BUS_ACQUIRED], 0, connection); } static void dbus_server_name_acquired_cb (GDBusConnection *connection, const gchar *bus_name, EDBusServer *server) { g_signal_emit (server, signals[BUS_NAME_ACQUIRED], 0, connection); } static void dbus_server_name_lost_cb (GDBusConnection *connection, const gchar *bus_name, EDBusServer *server) { g_signal_emit (server, signals[BUS_NAME_LOST], 0, connection); } static gboolean dbus_server_inactivity_timeout_cb (gpointer user_data) { EDBusServer *server = E_DBUS_SERVER (user_data); e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL); return FALSE; } #ifdef G_OS_UNIX static gboolean dbus_server_hang_up_cb (gpointer user_data) { EDBusServer *server = E_DBUS_SERVER (user_data); g_print ("Received hang up signal.\n"); e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_RELOAD); return FALSE; } static gboolean dbus_server_terminate_cb (gpointer user_data) { EDBusServer *server = E_DBUS_SERVER (user_data); g_print ("Received terminate signal.\n"); e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL); return FALSE; } #endif static void dbus_server_finalize (GObject *object) { EDBusServerPrivate *priv; priv = E_DBUS_SERVER_GET_PRIVATE (object); g_main_loop_unref (priv->main_loop); if (priv->bus_owner_id > 0) g_bus_unown_name (priv->bus_owner_id); if (priv->hang_up_id > 0) g_source_remove (priv->hang_up_id); if (priv->terminate_id > 0) g_source_remove (priv->terminate_id); if (priv->inactivity_timeout_id > 0) g_source_remove (priv->inactivity_timeout_id); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_dbus_server_parent_class)->finalize (object); } static void dbus_server_constructed (GObject *object) { e_dbus_server_load_modules (E_DBUS_SERVER (object)); e_extensible_load_extensions (E_EXTENSIBLE (object)); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_dbus_server_parent_class)->constructed (object); } static void dbus_server_bus_acquired (EDBusServer *server, GDBusConnection *connection) { if (server->priv->use_count == 0 && !server->priv->wait_for_client) { server->priv->inactivity_timeout_id = e_named_timeout_add_seconds ( INACTIVITY_TIMEOUT, (GSourceFunc) dbus_server_inactivity_timeout_cb, server); } } static void dbus_server_bus_name_acquired (EDBusServer *server, GDBusConnection *connection) { EDBusServerClass *class; class = E_DBUS_SERVER_GET_CLASS (server); g_return_if_fail (class->bus_name != NULL); g_print ("Bus name '%s' acquired.\n", class->bus_name); } static void dbus_server_bus_name_lost (EDBusServer *server, GDBusConnection *connection) { EDBusServerClass *class; class = E_DBUS_SERVER_GET_CLASS (server); g_return_if_fail (class->bus_name != NULL); g_print ("Bus name '%s' lost.\n", class->bus_name); e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL); } static EDBusServerExitCode dbus_server_run_server (EDBusServer *server) { EDBusServerClass *class; /* Try to acquire the well-known bus name. */ class = E_DBUS_SERVER_GET_CLASS (server); g_return_val_if_fail ( class->bus_name != NULL, E_DBUS_SERVER_EXIT_NONE); server->priv->bus_owner_id = g_bus_own_name ( G_BUS_TYPE_SESSION, class->bus_name, G_BUS_NAME_OWNER_FLAGS_REPLACE | G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, (GBusAcquiredCallback) dbus_server_bus_acquired_cb, (GBusNameAcquiredCallback) dbus_server_name_acquired_cb, (GBusNameLostCallback) dbus_server_name_lost_cb, g_object_ref (server), (GDestroyNotify) g_object_unref); g_main_loop_run (server->priv->main_loop); return server->priv->exit_code; } static void dbus_server_quit_server (EDBusServer *server, EDBusServerExitCode code) { /* If we're reloading, voluntarily relinquish our bus * name to avoid triggering a "bus-name-lost" signal. */ if (code == E_DBUS_SERVER_EXIT_RELOAD) { g_bus_unown_name (server->priv->bus_owner_id); server->priv->bus_owner_id = 0; } server->priv->exit_code = code; g_main_loop_quit (server->priv->main_loop); } static void ignore_log (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { /* Avoid printing of trivial messages while running test * cases. Only print warnings, criticals and errors. */ if ((log_level & (G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) != 0) g_log_default_handler (log_domain, log_level, message, user_data); } static void e_dbus_server_class_init (EDBusServerClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EDBusServerPrivate)); object_class = G_OBJECT_CLASS (class); object_class->finalize = dbus_server_finalize; object_class->constructed = dbus_server_constructed; class->bus_acquired = dbus_server_bus_acquired; class->bus_name_acquired = dbus_server_bus_name_acquired; class->bus_name_lost = dbus_server_bus_name_lost; class->run_server = dbus_server_run_server; class->quit_server = dbus_server_quit_server; /** * EDBusServer::bus-acquired: * @server: the #EDBusServer which emitted the signal * @connection: the #GDBusConnection to the session bus * * Emitted when @server acquires a connection to the session bus. **/ signals[BUS_ACQUIRED] = g_signal_new ( "bus-acquired", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EDBusServerClass, bus_acquired), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_DBUS_CONNECTION); /** * EDBusServer::bus-name-acquired: * @server: the #EDBusServer which emitted the signal * @connection: the #GDBusConnection to the session bus * * Emitted when @server acquires its well-known session bus name. **/ signals[BUS_NAME_ACQUIRED] = g_signal_new ( "bus-name-acquired", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EDBusServerClass, bus_name_acquired), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_DBUS_CONNECTION); /** * EDBusServer::bus-name-lost: * @server: the #EDBusServer which emitted the signal * @connection: the #GDBusconnection to the session bus, * or %NULL if the connection has been closed * * Emitted when @server loses its well-known session bus name * or the session bus connection has been closed. **/ signals[BUS_NAME_LOST] = g_signal_new ( "bus-name-lost", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EDBusServerClass, bus_name_lost), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_DBUS_CONNECTION); /** * EDBusServer::run-server: * @server: the #EDBusServer which emitted the signal * * Emitted to request that @server start its main loop and * attempt to acquire its well-known session bus name. * * Returns: an #EDBusServerExitCode **/ signals[RUN_SERVER] = g_signal_new ( "run-server", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EDBusServerClass, run_server), NULL, NULL, NULL, E_TYPE_DBUS_SERVER_EXIT_CODE, 0); /** * EDBusServer::quit-server: * @server: the #EDBusServer which emitted the signal * @code: an #EDBusServerExitCode * * Emitted to request that @server quit its main loop. **/ signals[QUIT_SERVER] = g_signal_new ( "quit-server", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EDBusServerClass, quit_server), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_DBUS_SERVER_EXIT_CODE); if (g_getenv ("EDS_TESTING") != NULL) g_log_set_default_handler (ignore_log, NULL); } static void e_dbus_server_init (EDBusServer *server) { server->priv = E_DBUS_SERVER_GET_PRIVATE (server); server->priv->main_loop = g_main_loop_new (NULL, FALSE); server->priv->wait_for_client = FALSE; #ifdef G_OS_UNIX server->priv->hang_up_id = g_unix_signal_add ( SIGHUP, dbus_server_hang_up_cb, server); server->priv->terminate_id = g_unix_signal_add ( SIGTERM, dbus_server_terminate_cb, server); #endif } /** * e_dbus_server_run: * @server: an #EDBusServer * @wait_for_client: continue running until a client connects * * Emits the #EDBusServer::run signal. * * By default the @server will start its main loop and attempt to acquire * its well-known session bus name. If the @server's main loop is already * running, the function will immediately return #E_DBUS_SERVER_EXIT_NONE. * Otherwise the function blocks until e_dbus_server_quit() is called. * * If @wait_for_client is %TRUE, the @server will continue running until * the first client connection is made instead of quitting on its own if * no client connection is made within the first few seconds. * * Returns: the exit code passed to e_dbus_server_quit() * * Since: 3.4 **/ EDBusServerExitCode e_dbus_server_run (EDBusServer *server, gboolean wait_for_client) { EDBusServerExitCode exit_code; g_return_val_if_fail ( E_IS_DBUS_SERVER (server), E_DBUS_SERVER_EXIT_NONE); server->priv->wait_for_client = wait_for_client; if (g_main_loop_is_running (server->priv->main_loop)) return E_DBUS_SERVER_EXIT_NONE; g_signal_emit (server, signals[RUN_SERVER], 0, &exit_code); return exit_code; } /** * e_dbus_server_quit: * @server: an #EDBusServer * @code: an #EDBusServerExitCode * * Emits the #EDBusServer::quit signal with the given @code. * * By default the @server will quit its main loop and cause * e_dbus_server_run() to return @code. * * Since: 3.4 **/ void e_dbus_server_quit (EDBusServer *server, EDBusServerExitCode code) { g_return_if_fail (E_IS_DBUS_SERVER (server)); g_signal_emit (server, signals[QUIT_SERVER], 0, code); } /** * e_dbus_server_hold: * @server: an #EDBusServer * * Increases the use count of @server. * * Use this function to indicate that the server has a reason to continue * to run. To cancel the hold, call e_dbus_server_release(). * * Since: 3.4 **/ void e_dbus_server_hold (EDBusServer *server) { g_return_if_fail (E_IS_DBUS_SERVER (server)); if (server->priv->inactivity_timeout_id > 0) { g_source_remove (server->priv->inactivity_timeout_id); server->priv->inactivity_timeout_id = 0; } server->priv->use_count++; } /** * e_dbus_server_release: * @server: an #EDBusServer * * Decreates the use count of @server. * * When the use count reaches zero, the server will stop running. * * Never call this function except to cancel the effect of a previous call * to e_dbus_server_hold(). * * Since: 3.4 **/ void e_dbus_server_release (EDBusServer *server) { g_return_if_fail (E_IS_DBUS_SERVER (server)); g_return_if_fail (server->priv->use_count > 0); server->priv->use_count--; if (server->priv->use_count == 0) { server->priv->inactivity_timeout_id = e_named_timeout_add_seconds ( INACTIVITY_TIMEOUT, (GSourceFunc) dbus_server_inactivity_timeout_cb, server); } } /** * e_dbus_server_load_modules: * @server: an #EDBusServer * * This function should be called once during @server initialization to * load all available library modules to extend the @server's functionality. * * Since: 3.4 **/ void e_dbus_server_load_modules (EDBusServer *server) { EDBusServerClass *class; gboolean already_loaded; const gchar *directory; GList *list; g_return_if_fail (E_IS_DBUS_SERVER (server)); class = E_DBUS_SERVER_GET_CLASS (server); g_return_if_fail (class->module_directory != NULL); /* This ensures a module directory is only loaded once. */ G_LOCK (directories_loaded); if (directories_loaded == NULL) directories_loaded = g_hash_table_new (NULL, NULL); directory = g_intern_string (class->module_directory); already_loaded = g_hash_table_contains (directories_loaded, directory); g_hash_table_add (directories_loaded, (gpointer) directory); G_UNLOCK (directories_loaded); if (already_loaded) return; list = e_module_load_all_in_directory (class->module_directory); g_list_free_full (list, (GDestroyNotify) g_type_module_unuse); }