/* * Copyright (C) 2008 Red Hat, Inc. * * This library 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) any later version. * * This library 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 this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. * * Author: David Zeuthen */ #include "config.h" #include #include "polkitbackendconfigsource.h" /* * SECTION:polkitbackendconfigsource * @title: PolkitBackendConfigSource * @short_description: Access configuration files * * The #PolkitBackendConfigSource class is a utility class to read * configuration data from a set of prioritized key-value files in a * given directory. */ struct _PolkitBackendConfigSourcePrivate { GFile *directory; GFileMonitor *directory_monitor; /* sorted according to priority, higher priority is first */ GList *key_files; gboolean has_data; }; enum { PROP_0, PROP_DIRECTORY, }; enum { CHANGED_SIGNAL, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; static void polkit_backend_config_source_purge (PolkitBackendConfigSource *source); static void polkit_backend_config_source_ensure (PolkitBackendConfigSource *source); G_DEFINE_TYPE (PolkitBackendConfigSource, polkit_backend_config_source, G_TYPE_OBJECT); /* ---------------------------------------------------------------------------------------------------- */ static void polkit_backend_config_source_init (PolkitBackendConfigSource *source) { source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, POLKIT_BACKEND_TYPE_CONFIG_SOURCE, PolkitBackendConfigSourcePrivate); } static void polkit_backend_config_source_finalize (GObject *object) { PolkitBackendConfigSource *source = POLKIT_BACKEND_CONFIG_SOURCE (object); if (source->priv->directory != NULL) g_object_unref (source->priv->directory); if (source->priv->directory_monitor != NULL) g_object_unref (source->priv->directory_monitor); g_list_foreach (source->priv->key_files, (GFunc) g_key_file_free, NULL); g_list_free (source->priv->key_files); if (G_OBJECT_CLASS (polkit_backend_config_source_parent_class)->finalize != NULL) G_OBJECT_CLASS (polkit_backend_config_source_parent_class)->finalize (object); } static void polkit_backend_config_source_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PolkitBackendConfigSource *source = POLKIT_BACKEND_CONFIG_SOURCE (object); switch (prop_id) { case PROP_DIRECTORY: g_value_set_object (value, source->priv->directory); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void polkit_backend_config_source_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PolkitBackendConfigSource *source = POLKIT_BACKEND_CONFIG_SOURCE (object); switch (prop_id) { case PROP_DIRECTORY: source->priv->directory = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void directory_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { PolkitBackendConfigSource *source; source = POLKIT_BACKEND_CONFIG_SOURCE (user_data); if (file != NULL) { gchar *name; name = g_file_get_basename (file); //g_debug ("event_type=%d file=%p name=%s", event_type, file, name); if (!g_str_has_prefix (name, ".") && !g_str_has_prefix (name, "#") && g_str_has_suffix (name, ".conf") && (event_type == G_FILE_MONITOR_EVENT_CREATED || event_type == G_FILE_MONITOR_EVENT_DELETED || event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) { //g_debug ("match"); /* now throw away all caches */ polkit_backend_config_source_purge (source); g_signal_emit_by_name (source, "changed"); } g_free (name); } } static void polkit_backend_config_source_constructed (GObject *object) { PolkitBackendConfigSource *source = POLKIT_BACKEND_CONFIG_SOURCE (object); GError *error; error = NULL; source->priv->directory_monitor = g_file_monitor_directory (source->priv->directory, G_FILE_MONITOR_NONE, NULL, &error); if (source->priv->directory_monitor == NULL) { gchar *dir_name; dir_name = g_file_get_uri (source->priv->directory); g_warning ("Error monitoring directory %s: %s", dir_name, error->message); g_free (dir_name); g_error_free (error); } else { g_signal_connect (source->priv->directory_monitor, "changed", (GCallback) directory_monitor_changed, source); } if (G_OBJECT_CLASS (polkit_backend_config_source_parent_class)->constructed != NULL) G_OBJECT_CLASS (polkit_backend_config_source_parent_class)->constructed (object); } static void polkit_backend_config_source_class_init (PolkitBackendConfigSourceClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = polkit_backend_config_source_get_property; gobject_class->set_property = polkit_backend_config_source_set_property; gobject_class->constructed = polkit_backend_config_source_constructed; gobject_class->finalize = polkit_backend_config_source_finalize; g_type_class_add_private (klass, sizeof (PolkitBackendConfigSourcePrivate)); /** * PolkitBackendConfigSource:directory: * * The directory to watch for configuration files. */ g_object_class_install_property (gobject_class, PROP_DIRECTORY, g_param_spec_object ("directory", "Directory", "The directory to watch for configuration files", G_TYPE_FILE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); /** * PolkitBackendConfiguSource::changed: * @source: A #PolkitBackendConfigSource. * * Emitted when configuration files in #PolkitBackendConfiguSource:directory changes. */ signals[CHANGED_SIGNAL] = g_signal_new ("changed", POLKIT_BACKEND_TYPE_CONFIG_SOURCE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PolkitBackendConfigSourceClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } /** * polkit_backend_config_source_new: * @directory: The directory to watch. * * Creates a new #PolkitBackendConfigSource object that reads * configuration from @directory. To watch for configuration changes, * connect to the #PolkitBackendConfigSource::changed signal. * * Returns: A #PolkitBackendConfigSource for @directory. Free with * g_object_unref(). **/ PolkitBackendConfigSource * polkit_backend_config_source_new (GFile *directory) { PolkitBackendConfigSource *source; source = POLKIT_BACKEND_CONFIG_SOURCE (g_object_new (POLKIT_BACKEND_TYPE_CONFIG_SOURCE, "directory", directory, NULL)); return source; } static void polkit_backend_config_source_purge (PolkitBackendConfigSource *source) { g_list_foreach (source->priv->key_files, (GFunc) g_key_file_free, NULL); g_list_free (source->priv->key_files); source->priv->key_files = NULL; source->priv->has_data = FALSE; } static gint compare_filename (GFile *a, GFile *b) { gchar *a_uri; gchar *b_uri; gint ret; a_uri = g_file_get_uri (a); b_uri = g_file_get_uri (b); /* TODO: use ASCII sort function? */ ret = -g_strcmp0 (a_uri, b_uri); return ret; } static void polkit_backend_config_source_ensure (PolkitBackendConfigSource *source) { GFileEnumerator *enumerator; GFileInfo *file_info; GError *error; GList *files; GList *l; files = NULL; if (source->priv->has_data) goto out; polkit_backend_config_source_purge (source); error = NULL; enumerator = g_file_enumerate_children (source->priv->directory, "standard::name", G_FILE_QUERY_INFO_NONE, NULL, &error); if (enumerator == NULL) { gchar *dir_name; dir_name = g_file_get_uri (source->priv->directory); g_warning ("Error enumerating files in %s: %s", dir_name, error->message); g_free (dir_name); g_error_free (error); goto out; } while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL) { const gchar *name; name = g_file_info_get_name (file_info); /* only consider files ending in .conf */ if (g_str_has_suffix (name, ".conf")) files = g_list_prepend (files, g_file_get_child (source->priv->directory, name)); g_object_unref (file_info); } g_object_unref (enumerator); if (error != NULL) { g_warning ("Error enumerating files: %s", error->message); g_error_free (error); goto out; } files = g_list_sort (files, (GCompareFunc) compare_filename); /* process files; highest priority comes first */ for (l = files; l != NULL; l = l->next) { GFile *file = G_FILE (l->data); gchar *filename; GKeyFile *key_file; filename = g_file_get_path (file); key_file = g_key_file_new (); error = NULL; if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL)) { g_warning ("Error loading key-file %s: %s", filename, error->message); g_error_free (error); error = NULL; g_key_file_free (key_file); } else { source->priv->key_files = g_list_prepend (source->priv->key_files, key_file); } g_free (filename); } source->priv->key_files = g_list_reverse (source->priv->key_files); source->priv->has_data = TRUE; out: g_list_foreach (files, (GFunc) g_object_unref, NULL); g_list_free (files); } static GKeyFile * find_key_file (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GList *l; GKeyFile *ret; ret = NULL; for (l = source->priv->key_files; l != NULL; l = l->next) { GKeyFile *key_file = l->data; if (g_key_file_has_key (key_file, group, key, NULL)) { ret = key_file; goto out; } } out: if (ret == NULL) g_set_error_literal (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "Group/Key combo not found in any config file"); return ret; } /** * polkit_backend_config_source_get_integer: * @source: A PolkitBackendConfigSource. * @group: A group name. * @key: A key name. * @error: Return location for error or %NULL. * * Gets the value associated with @key under @group_name. * * Returns: The value or 0 if @error is set. **/ gint polkit_backend_config_source_get_integer (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GKeyFile *key_file; polkit_backend_config_source_ensure (source); key_file = find_key_file (source, group, key, error); if (key_file == NULL) return 0; return g_key_file_get_integer (key_file, group, key, error); } /** * polkit_backend_config_source_get_boolean: * @source: A PolkitBackendConfigSource. * @group: A group name. * @key: A key name. * @error: Return location for error or %NULL. * * Gets the value associated with @key under @group_name. * * Returns: The value or %FALSE if @error is set. **/ gboolean polkit_backend_config_source_get_boolean (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GKeyFile *key_file; polkit_backend_config_source_ensure (source); key_file = find_key_file (source, group, key, error); if (key_file == NULL) return FALSE; return g_key_file_get_boolean (key_file, group, key, error); } /** * polkit_backend_config_source_get_double: * @source: A PolkitBackendConfigSource. * @group: A group name. * @key: A key name. * @error: Return location for error or %NULL. * * Gets the value associated with @key under @group_name. * * Returns: The value or 0.0 if @error is set. **/ gdouble polkit_backend_config_source_get_double (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GKeyFile *key_file; polkit_backend_config_source_ensure (source); key_file = find_key_file (source, group, key, error); if (key_file == NULL) return 0.0; return g_key_file_get_double (key_file, group, key, error); } /** * polkit_backend_config_source_get_string: * @source: A PolkitBackendConfigSource. * @group: A group name. * @key: A key name. * @error: Return location for error or %NULL. * * Gets the value associated with @key under @group_name. * * Returns: The value or %NULL if @error is set. **/ gchar * polkit_backend_config_source_get_string (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GKeyFile *key_file; polkit_backend_config_source_ensure (source); key_file = find_key_file (source, group, key, error); if (key_file == NULL) return NULL; return g_key_file_get_string (key_file, group, key, error); } /** * polkit_backend_config_source_get_string_list: * @source: A PolkitBackendConfigSource. * @group: A group name. * @key: A key name. * @error: Return location for error or %NULL. * * Gets the values associated with @key under @group_name. * * Returns: The value or %NULL if @error is set. **/ gchar ** polkit_backend_config_source_get_string_list (PolkitBackendConfigSource *source, const gchar *group, const gchar *key, GError **error) { GKeyFile *key_file; polkit_backend_config_source_ensure (source); key_file = find_key_file (source, group, key, error); if (key_file == NULL) return NULL; return g_key_file_get_string_list (key_file, group, key, NULL, error); }