Logo Search packages:      
Sourcecode: sabayon version File versions  Download package


#!/usr/bin/env python

# Copyright (C) 2005 Red Hat, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

import os
import os.path
import shutil
import time
import errno
import gobject
import gconf
import fnmatch

    import userprofile
    import storage
    import util
    from config import *
    from sabayon import userprofile
    from sabayon import storage
    from sabayon import util
    from sabayon.config import *

def dprint (fmt, *args):
    util.debug_print (util.DEBUG_GCONFSOURCE, fmt % args)

# gconf_engine_associate_schema() isn't wrapped
def associate_schema (config_source, key, schema_key):
    os.system ("gconftool-2 --config-source='%s' --apply-schema %s %s" % (config_source, schema_key, key))

def copy_tree (src_client, dst_client, dir):
    for entry in src_client.all_entries (dir):
        if entry.value:
            dst_client.set (entry.key, entry.value)
    for subdir in src_client.all_dirs (dir):
        copy_tree (src_client, dst_client, subdir)

# No mapping for gconf_client_recursive_unset()
def recursive_unset (client, dir):
    for entry in client.all_entries (dir):
        client.unset (entry.key)
    for subdir in client.all_dirs (dir):
        recursive_unset (client, subdir)

def get_client_and_address_for_path (path):
        os.makedirs (path)
    except OSError, err:
        if err.errno != errno.EEXIST:
            raise err
    address = "xml:readwrite:" + path
    engine = gconf.engine_get_for_address (address)
    return (gconf.client_get_for_engine (engine), address)

00072 class GConfChange (userprofile.ProfileChange):
    """Encapsulates a change to a GConf key."""
00075     def __init__ (self, source, key, value):
        """Construct a GConfChange from a GConfEntry."""
        userprofile.ProfileChange.__init__ (self, source)
        self.key   = key
        self.value = value
        self.mandatory = None

00082     def get_id (self):
        """Return the path to the GConf key which changed."""
        return self.key

00086     def get_short_description (self):
        """Return a short description of the GConf key change."""
        if not self.value:
            return _("GConf key '%s' unset") % self.key
        elif self.value.type == gconf.VALUE_STRING:
            return _("GConf key '%s' set to string '%s'")  % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_INT:
            return _("GConf key '%s' set to integer '%s'") % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_FLOAT:
            return _("GConf key '%s' set to float '%s'")   % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_BOOL:
            return _("GConf key '%s' set to boolean '%s'") % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_SCHEMA:
            return _("GConf key '%s' set to schema '%s'")  % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_LIST:
            return _("GConf key '%s' set to list '%s'")    % (self.key, self.value.to_string ())
        elif self.value.type == gconf.VALUE_PAIR:
            return _("GConf key '%s' set to pair '%s'")    % (self.key, self.value.to_string ())
            return _("GConf key '%s' set to '%s'")         % (self.key, self.value.to_string ())

    def set_mandatory (self, value):
        self.mandatory = value
00110     def get_mandatory (self):
        return self.mandatory

gobject.type_register (GConfChange)

00115 class GConfSource (userprofile.ProfileSource):
    """GConf user profile source."""
00118     def __init__ (self, storage):
        """Construct a GConfSource

        @storage: storage object
        userprofile.ProfileSource.__init__ (self, _("GConf"), "get_gconf_delegate")

        self.storage              = storage
        self.home_dir             = util.get_home_dir ()
        self.client               = None
        self.notify_id            = 0
        self.defaults_client      = None
        self.mandatory_client     = None
        self.mandatory_alt_client = None
        self.enforce_mandatory    = True

00134     def get_path_description (self, path):
        if path == ".gconf.xml.defaults":
            return _("Default GConf settings")
        elif path == ".gconf.xml.mandatory":
            return _("Mandatory GConf settings")
            return path

00142     def get_committing_client_and_address (self, mandatory):
        """Get a GConfClient using either .gconf.xml.defaults or
        .gconf.xml.mandatory (in the temporary profile location)
        as its source.

        mandatory: whether to get the mandatory or defaults source
        if not mandatory:
            if not self.defaults_client:
                (client, address) = get_client_and_address_for_path (os.path.join (self.home_dir, ".gconf.xml.defaults"))
                self.defaults_client = client
                self.defaults_address = address
            return (self.defaults_client, self.defaults_address)
            if self.enforce_mandatory:
                if not self.mandatory_client:
                    (client, address) = get_client_and_address_for_path (os.path.join (self.home_dir, ".gconf.xml.mandatory"))
                    self.mandatory_client = client
                    self.mandatory_address = address
                return (self.mandatory_client, self.mandatory_address)
                if not self.mandatory_alt_client:
                    (client, address) = get_client_and_address_for_path (os.path.join (self.home_dir, ".gconf.xml.mandatory-alt"))
                    self.mandatory_alt_client = client
                    self.mandatory_alt_address = address
                return (self.mandatory_alt_client, self.mandatory_alt_address)

00170     def commit_change (self, change, mandatory = False):
        """Commit a GConf change to the profile."""
        if userprofile.ProfileSource.commit_change (self, change, mandatory):
        (client, address) = self.get_committing_client_and_address (mandatory)

        dprint ("Committing change to '%s' to '%s'", change.key, address)
        if change.value:
            client.set (change.key, change.value)
            client.unset (change.key)

        # Make sure to unset the other sabayon gconf database, as we may be changing
        # the key from mandatory to non-mandatory
        (client, address) = self.get_committing_client_and_address (not mandatory)
        client.unset (change.key)
00189     def start_monitoring (self):
        """Start monitoring for GConf changes. Note that this
        is seriously resource intensive as must load the value
        of all existing keys so that we can determine whether
        a write to the database resulted in an actual change
        in the value of the key.
        if self.notify_id != 0:
        def handle_notify (client, cnx_id, entry, self):
            dprint ("Got GConf notification on '%s'", entry.key)
            for ignore_pattern in GCONF_KEYS_TO_IGNORE:
                if fnmatch.fnmatchcase (entry.key, ignore_pattern):
                    dprint ("Ignoring GConf notification on '%s' because it matches '%s'",
                            entry.key, ignore_pattern)
            value = None
            if not entry.get_is_default ():
                value = entry.value
            self.emit_change (GConfChange (self, entry.key, value))

        # Only monitor for changes in the user settings database
        (self.client, address) = get_client_and_address_for_path (os.path.join (self.home_dir, ".gconf"))
        self.client.add_dir ("/", gconf.CLIENT_PRELOAD_RECURSIVE)
        self.notify_id = self.client.notify_add ("/", handle_notify, self)

00219     def stop_monitoring (self):
        """Stop monitoring for GConf changes."""
        if self.notify_id == 0:

        self.client.notify_remove (self.notify_id)
        self.notify_id = 0
        self.client.remove_dir ("/")
        self.client = None

00229     def sync_changes (self):
        """Ensure that all committed changes are saved to disk."""
        # FIXME: it would be nicer if we just wrote directly
        #        to the defaults and mandatory sources
        #dprint ("Shutting down gconfd in order to sync changes to disk")
        #os.system ("gconftool-2 --shutdown")
        if self.defaults_client:
        if self.mandatory_client:
        if self.mandatory_alt_client:
        time.sleep (2)

        if os.path.exists (os.path.join (self.home_dir, ".gconf.xml.defaults")):
            self.storage.add (".gconf.xml.defaults", self.home_dir, self.name)

        if self.enforce_mandatory:
            mandatory_src = ".gconf.xml.mandatory"
            mandatory_src = ".gconf.xml.mandatory-alt"
        if os.path.exists (os.path.join (self.home_dir, mandatory_src)):
            self.storage.add (".gconf.xml.mandatory", self.home_dir, self.name, src_path = mandatory_src)

00255     def set_enforce_mandatory (self, enforce):
        if enforce == self.enforce_mandatory:

        dprint ("Setting enforce mandatory to %d", enforce)
        (old_client, old_address) = self.get_committing_client_and_address (True)
        self.enforce_mandatory = enforce
        (client, address) = self.get_committing_client_and_address (True)

        copy_tree (old_client, client, "/")
        recursive_unset (old_client, "/")

00268     def apply (self, is_sabayon_session):
        """Apply the profile by writing the default and mandatory
        sources location to ~/.gconf.path.defaults and

        Note that $(sysconfdir)/gconf/2/path needs to contain
        something like the following in order for this to work:

        include $(HOME)/.gconf.path.mandatory
        include $(HOME)/.gconf.path.defaults
        def write_path_file (filename, source):
            """Write a GConf path file. First try writing to a
            temporary file and move it over the original. Failing
            that, write directly to the original.
            dprint ("Writing GConf path file with '%s' to '%s'", source, filename)
            temp = filename + ".new"
                f = file (temp, "w")
                temp = None
                f = file (filename, "w")

                f.write (source + "\n")
                f.close ()
                if temp != None:
                    os.remove (temp)

            if temp != None:
                os.rename (temp, filename)

        storage_contents = self.storage.list (self.name)

        if ("GConf", ".gconf.xml.defaults") in storage_contents:
            self.storage.extract (".gconf.xml.defaults", self.home_dir, True)
        default_path = "xml:readonly:" + os.path.join (self.home_dir, ".gconf.xml.defaults");
        if is_sabayon_session:
            default_path = "xml:readonly:" + os.path.join (self.home_dir, ".gconf.xml.mandatory-alt") + "\n" + default_path
        write_path_file (os.path.join (self.home_dir, ".gconf.path.defaults"), default_path)
        if ("GConf", ".gconf.xml.mandatory") in storage_contents:
            self.storage.extract (".gconf.xml.mandatory", self.home_dir, True)
        write_path_file (os.path.join (self.home_dir, ".gconf.path.mandatory"),
                         "xml:readonly:" + os.path.join (self.home_dir, ".gconf.xml.mandatory"))

        # FIXME: perhaps just kill -HUP it? It would really just be better
        #        if we could guarantee that there wasn't a gconfd already
        #        running.
        dprint ("Shutting down gconfd so it kill pick up new paths")
        os.system ("gconftool-2 --shutdown")

    def add_gconf_notify (self, key, handler, data):
        return self.client.notify_add (key, handler, data)

    def remove_gconf_notify (self, id):
        return self.client.notify_remove (id)

    def get_gconf_key_is_mandatory (self, key):
        (client, address) = self.get_committing_client_and_address (True)
        entry = client.get_entry (key, "", True)
        if entry and entry.value:
            return True
        return False

    def set_gconf_boolean (self, key, value, mandatory):
        gconf_value = gconf.Value (gconf.VALUE_BOOL)
        gconf_value.set_bool (value)
        change = GConfChange (self, key, gconf_value)
        change.set_mandatory (mandatory)
        self.client.set_bool (key, value)
        self.emit_change (change)

    def set_gconf_list (self, key, list_type, value, mandatory):
        gconf_value = gconf.Value (gconf.VALUE_LIST)
        list = []
        for item in value:
            item_value = gconf.Value (list_type)
            if list_type == gconf.VALUE_STRING:
                item_value.set_string (item)
                raise NotImplementedError
            list.append (item_value)
        gconf_value.set_list_type (list_type)
        gconf_value.set_list (list)
        change = GConfChange (self, key, gconf_value)
        change.set_mandatory (mandatory)
        self.client.set_list (key, list_type, value)
        self.emit_change (change)

gobject.type_register (GConfSource)

def get_source (storage):
    return GConfSource (storage)

# Unit tests
def run_unit_tests ():
    main_loop = gobject.MainLoop ()

    profile_path = os.path.join (os.getcwd (), "gconf-test.zip")
    if os.path.exists (profile_path):
        os.remove (profile_path)

    source = get_source (storage.ProfileStorage ("GConfTest"))

    # Remove any stale path files
        os.remove (os.path.join (util.get_home_dir (), ".gconf.path.defaults"))
        os.remove (os.path.join (util.get_home_dir (), ".gconf.path.mandatory"))

    # Need to shutdown the daemon to ensure its not using stale paths
    os.system ("gconftool-2 --shutdown")
    time.sleep (1)

    # Make sure there's no stale keys from a previous run
    # FIXME: gconf_client_recursive_unset() has no wrapping
    # source.client.recursive_unset ("/tmp/test-gconfprofile")
    os.system ("gconftool-2 --recursive-unset /tmp/test-gconfprofile")
    time.sleep (1)

    global changes
    changes = []
    def handle_changed (source, change):
        global changes
        changes.append (change)
    source.connect ("changed", handle_changed)

    source.start_monitoring ()

    # Need to run the mainloop to get notifications.
    # The notification is only dispatched once the set
    # operation has complete
    # We poll after each set because otherwise GConfClient
    # will dispatch the two notifications for the same key
    def poll (main_loop):
        while main_loop.get_context ().pending ():
            main_loop.get_context ().iteration (False)
    source.client.set_bool ("/tmp/test-gconfprofile/t1", True)
    poll (main_loop)
    source.client.set_bool ("/tmp/test-gconfprofile/t1", False)
    poll (main_loop)
    source.client.set_bool ("/tmp/test-gconfprofile/t2", True)
    poll (main_loop)
    source.client.set_int ("/tmp/test-gconfprofile/t3", 3)
    poll (main_loop)
    source.stop_monitoring ()
    source.client = gconf.client_get_default ()
    assert len (changes) == 4
    assert changes[3].key == "/tmp/test-gconfprofile/t3"
    source.commit_change (changes[3])
    assert changes[2].key == "/tmp/test-gconfprofile/t2"
    source.commit_change (changes[2], True)
    assert changes[1].key == "/tmp/test-gconfprofile/t1"
    assert changes[0].key == "/tmp/test-gconfprofile/t1"

    # source.client.recursive_unset ("/tmp/test-gconfprofile")
    os.system ("gconftool-2 --recursive-unset /tmp/test-gconfprofile")
    source.sync_changes ()
    source.apply (False)

    assert os.access (os.path.join (util.get_home_dir (), ".gconf.path.defaults"), os.F_OK)
    assert os.access (os.path.join (util.get_home_dir (), ".gconf.path.mandatory"), os.F_OK)

    # We need to clear the cache because GConfClient doesn't know
    # some new sources have been added to the sources stack so it
    # won't see the value we put in the mandatory source
    source.client.clear_cache ()
    entry = source.client.get_entry ("/tmp/test-gconfprofile/t3", "", False)
    assert entry.value
    assert entry.value.type == gconf.VALUE_INT
    assert entry.value.get_int () == 3
    assert not entry.get_is_default ()
    assert entry.get_is_writable ()
    entry = source.client.get_entry ("/tmp/test-gconfprofile/t2", "", False)
    assert entry.value
    assert entry.value.type == gconf.VALUE_BOOL
    assert entry.value.get_bool () == True
    assert not entry.get_is_default ()
    assert not entry.get_is_writable ()

    # Shutdown the daemon and remove the path files so we don't screw
    # too much with the running session
    os.system ("gconftool-2 --shutdown")
    time.sleep (1)

    os.remove (os.path.join (util.get_home_dir (), ".gconf.path.defaults"))
    os.remove (os.path.join (util.get_home_dir (), ".gconf.path.mandatory"))
    if os.path.exists (profile_path):
        os.remove (profile_path)

Generated by  Doxygen 1.6.0   Back to index