[dconf-editor] Add support for manually allocating schemas



commit b07a04d373fbc1a238ecca34c082df039a21c37a
Author: Davi da Silva Böger <dsboger gmail com>
Date:   Sun Nov 26 06:06:23 2017 -0200

    Add support for manually allocating schemas
    
    Add an option "manual-schemas", with type a{ss}, that is inspected
    when parsing schemas. For each entry, left-side is a relocatable schema
    ID and right-side is a "path specification", i.e. absolute paths that
    allow empty segments (//).
    
    The indicated schema is applied to every path in the tree that matches
    the path spec in the right-side. Empty path segments (//) function as
    wild card in the matching procedure.
    
    The matching procedure matches existing paths (discovered previously
    through non-relocatable schemas and DConfClient), but also creates new
    paths if the specs are definite enough (i.e. unless it contains some
    wild card for which there are none existing paths).
    
    Value example:
    { 'org.gnome.desktop.app-folders.folder':
            '/org/gnome/desktop/app-folders/folders//' }

 editor/ca.desrt.dconf-editor.gschema.xml |    5 +
 editor/dconf-model.vala                  |  150 +++++++++++++++++++++++++++++-
 editor/dconf-window.vala                 |    4 +-
 3 files changed, 157 insertions(+), 2 deletions(-)
---
diff --git a/editor/ca.desrt.dconf-editor.gschema.xml b/editor/ca.desrt.dconf-editor.gschema.xml
index 4d59d8e..6ffaedd 100644
--- a/editor/ca.desrt.dconf-editor.gschema.xml
+++ b/editor/ca.desrt.dconf-editor.gschema.xml
@@ -100,6 +100,11 @@
       <summary>Mouse button to activate the “Forward” command in browser window</summary>
       <description>For users with mice that have buttons for “Forward” and “Back”, this key will set which 
button activates the “Forward” command in a browser window. Possible values range between 6 and 
14.</description>
     </key>
+    <key name="manual-schemas" type="a{ss}">
+      <default>{}</default>
+      <summary>Mapping of paths to manually associated schemas</summary>
+      <description>A dictionary that maps schema IDs with path specifications. It is used to allow the user 
to associate a relocatable schema to certain paths. Path specifications may contain wildcards in the form of 
empty segments (e.g /ca/desrt/dconf-editor//), defining possibly multiple paths. The same schema ID may be 
associated with multiple path specifications.</description>
+    </key>
   </schema>
   <enum id="ca.desrt.dconf-editor.DemoEnum">
     <value value="0" nick="Red"/>
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
index 391421d..55f6b6a 100644
--- a/editor/dconf-model.vala
+++ b/editor/dconf-model.vala
@@ -81,6 +81,16 @@ public class Directory : SettingObject
         return child_map.lookup (name);
     }
 
+    public string[] get_children ()
+    {
+        if (key_model_accessed)
+            assert_not_reached ();
+        string[] names = new string [child_map.size ()];
+        int i = 0;
+        child_map.get_values ().foreach ((dir) => names [i++] = dir.name);
+        return names;
+    }
+
     /*\
     * * Folders creation
     \*/
@@ -556,13 +566,31 @@ public class GSettingsKey : Key
     } */
 }
 
+class ManualSchemaInfo
+{
+    public SettingsSchema? schema;
+    public List<PathSpec> path_specs; // FIXME? cannot have a List<string[]>
+}
+
+class PathSpec
+{
+    public string[] segments;
+    public PathSpec (string[] segs)
+    {
+        segments = segs;
+    }
+}
+
 public class SettingsModel : Object
 {
+    private Settings application_settings;
     private DConf.Client client = new DConf.Client ();
     private Directory root;
 
-    public SettingsModel ()
+    public SettingsModel (Settings application_settings)
     {
+        this.application_settings = application_settings;
+
         SettingsSchemaSource? settings_schema_source = SettingsSchemaSource.get_default ();
         root = new Directory ("/", "/", client);
 
@@ -571,6 +599,9 @@ public class SettingsModel : Object
 
         create_dconf_views (root);
 
+        if (settings_schema_source != null)
+            create_manual_schemas_views (parse_manual_schemas ((!) settings_schema_source));
+
         client.watch_sync ("/");
     }
 
@@ -594,6 +625,54 @@ public class SettingsModel : Object
         }
     }
 
+    private HashTable<string, ManualSchemaInfo> parse_manual_schemas (SettingsSchemaSource 
settings_schema_source)
+    {
+        HashTable<string, ManualSchemaInfo> manual_schemas = new HashTable<string, ManualSchemaInfo> 
(str_hash, str_equal);
+
+        Variant manual_schemas_variant = application_settings.get_value ("manual-schemas");
+        VariantIter entries_iter;
+        manual_schemas_variant.get ("a{ss}", out entries_iter);
+        string schema_id;
+        string path_spec;
+        while (entries_iter.next ("{ss}", out schema_id, out path_spec))
+        {
+            SettingsSchema? settings_schema;
+            ManualSchemaInfo? schema_info = manual_schemas.lookup (schema_id);
+            if (schema_info == null)
+            {
+                schema_info = new ManualSchemaInfo ();
+                settings_schema = settings_schema_source.lookup (schema_id, true);
+                ((!) schema_info).schema = settings_schema;
+                ((!) schema_info).path_specs = new List<PathSpec> ();
+                manual_schemas.insert (schema_id, (!) schema_info);
+            }
+            else
+                settings_schema = ((!) schema_info).schema;
+            if (settings_schema == null || ((string?) ((!) settings_schema).get_path ()) != null) // TODO 
report bug, get_path () should be string? (this is not a question :)
+                continue;
+
+            if (path_spec == "")
+                continue;
+            if (!path_spec.has_prefix ("/"))
+                path_spec = "/" + path_spec;
+            if (!path_spec.has_suffix ("/"))
+                path_spec += "/"; // TODO proper validation
+
+            if (!("//" in path_spec)) // no wild cards, just create the views
+            {
+                Directory view = create_gsettings_views (root, path_spec [1:path_spec.length]);
+                view.init_gsettings_keys ((!) settings_schema);
+            }
+            else // try to fill the holes later in create_manual_schemas_views
+                ((!) schema_info).path_specs.append (new PathSpec (path_spec [1:-1].split ("/")));
+        }
+        // remove useless entries
+        manual_schemas.foreach_remove ((schema_id, info) => {
+                return info.schema == null || info.path_specs.length () == 0;
+            });
+        return manual_schemas;
+    }
+
     /*\
     * * Recursive creation of views (directories)
     \*/
@@ -616,6 +695,75 @@ public class SettingsModel : Object
                 create_dconf_views (get_child (view, item [0:-1]));
     }
 
+    private Queue<Directory> search_nodes = new Queue<Directory> ();
+
+    private void create_manual_schemas_views (HashTable<string, ManualSchemaInfo> manual_schemas)
+    {
+        search_nodes.clear (); // subtrees that need yet to be matched against the path specs
+        search_nodes.push_head (root); // start with the whole known tree
+        while (!search_nodes.is_empty ())
+        {
+            Directory subtree = search_nodes.pop_tail ();
+            string subtree_path = subtree.full_name;
+            string[] subtree_segments = subtree_path == "/" ? new string [0] : subtree_path [1:-1].split 
("/");
+            manual_schemas.get_values ().foreach ((schema_info) => {
+                    schema_info.path_specs.foreach ((spec) => {
+                            string[] spec_segments = spec.segments;
+                            if (subtree_segments.length > spec_segments.length)
+                                return; // spec too short, cannot match anything below the subtree
+                            int matched_prefix_length = 0;
+                            while (matched_prefix_length < subtree_segments.length)
+                            {
+                                if (spec_segments [matched_prefix_length] != ""
+                                        && spec_segments [matched_prefix_length] != subtree_segments 
[matched_prefix_length])
+                                    return; // mismatch in one of the subtree ancestors, spec incompatible 
with subtree
+                                matched_prefix_length++;
+                            }
+                            // search subtree recursively for matches with spec. the search may queue new 
paths for further iterations
+                            create_manual_schemas_views_in_subtree (subtree, spec_segments, 
matched_prefix_length, (!) schema_info.schema);
+                        });
+                });
+        }
+    }
+
+    private void create_manual_schemas_views_in_subtree (Directory view, string[] spec_segments, int 
matched_prefix_length, SettingsSchema schema)
+    {
+        Directory parent = view;
+        Directory? child = null;
+        while (matched_prefix_length < spec_segments.length
+                && spec_segments [matched_prefix_length] != ""
+                && (child = parent.lookup_directory (spec_segments [matched_prefix_length])) != null)
+        {
+            matched_prefix_length++;
+            parent = (!) child;
+        }
+
+        if (matched_prefix_length == spec_segments.length)
+            parent.init_gsettings_keys (schema); // matched the whole spec to an already existing path
+        else if (spec_segments [matched_prefix_length] == "")
+        {
+            // wild card found, must branch out and match against existing children (there might be none)
+            foreach (string name in parent.get_children ())
+                create_manual_schemas_views_in_subtree ((!) parent.lookup_directory (name), spec_segments, 
matched_prefix_length + 1, schema);
+        }
+        else if (child == null)
+        {
+            // reached a dead end, if the rest of the spec has no wild cards, we add this branch to the tree
+            for (int i = matched_prefix_length; i < spec_segments.length; i++) // must test this before 
creating
+                if (spec_segments [i] == "")
+                    return; // further wild cards can only be matched by existing folders, but we already 
reached a dead end, abort
+            // the spec contains all the final folder names, so we can create them
+            for (int i = matched_prefix_length; i < spec_segments.length; i++)
+            {
+                parent = get_child (parent, spec_segments [i]);
+                search_nodes.push_head (parent); // a created node may be matched by a wild card in another 
spec, so we push it into the queue
+            }
+            parent.init_gsettings_keys (schema);
+        }
+        else
+            assert_not_reached ();
+    }
+
     private Directory get_child (Directory parent_view, string name)
     {
         Directory? view = parent_view.lookup_directory (name);
diff --git a/editor/dconf-window.vala b/editor/dconf-window.vala
index 9ce224c..0b9f6c6 100644
--- a/editor/dconf-window.vala
+++ b/editor/dconf-window.vala
@@ -31,7 +31,7 @@ class DConfWindow : ApplicationWindow
 
     public string current_path { get; set; default = "/"; } // not synced bidi, needed for saving on 
destroy, even after child destruction
 
-    public SettingsModel model { get; private set; default=new SettingsModel (); }
+    public SettingsModel model { get; private set; }
 
     private int window_width = 0;
     private int window_height = 0;
@@ -74,6 +74,8 @@ class DConfWindow : ApplicationWindow
 
     public DConfWindow (string? path)
     {
+        model = new SettingsModel (settings);
+
         add_action_entries (action_entries, this);
 
         set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]