DataModel.cs



Hi,

To get started on Tomboy I wrote DataModel.cs, which exposes the "Online Desktop Engine" data model to C#. When you're done laughing at my C# incompetence this is maybe a useful illustration of how the data model works.

Owen gives more background at http://fishsoup.net/blog/2007/07/24/#19 , the idea of this mail is to show how it ends up looking as an API.

There is really only one method involved, on a DataModel singleton object:

  public Resource QueryResource(string resourceId,
                                string fetchString);

Then a Resource is essentially a dictionary (bag of properties):

  public object GetProperty(string namespaceUri,
                            string unqualifiedName);
  public object GetProperty(string unqualifiedName);

There's also a Changed signal. Properties can themselves be Resource objects, so there's a tree (graph?) of objects.

Implementation goes
 D-Bus -> Online Desktop Engine service -> XMPP -> online.gnome.org

The "Changed" signal originates with online.gnome.org, that is if anyone changes the object in the database, then all desktops are notified.

The elaboration I haven't written yet for DataModel.cs is to support generating a proxy for a Resource, i.e. you define an interface:

interface User
{
     string Name
     {
         get;
     }
}

Then you annotate Name with the property name on a Resource object it maps to.

At that point we can auto-create a User object that is tied live to a server-side object. Pretty fun.

Havoc

/* -*- mode: csharp; c-file-style: "bsd"; indent-tabs-mode: nil; c-basic-offset: 8; -*- */

using System;
using System.Collections;
using System.Collections.Generic;
using NDesk.DBus;
using org.freedesktop.DBus;

namespace Freedesktop.OnlineDesktop
{        
        public class DataModel
        {
		/* one instance per server name */
                static Dictionary<string,DataModel> instances = new Dictionary<string,DataModel>();

		public static DataModel GetForServer(string serverName)
                {
                        // FIXME put a thread lock around this

                        DataModel dm = null;
                        if (instances.TryGetValue(serverName, out dm))
                        {
                                return dm;
                        }
                        else
                        {
                                dm = new DataModel(serverName);
                                instances.Add(dm.ServerName, dm);
                                return dm;
                        }
                }

                string serverName;
                Model model;
                SimpleModelClient client;
                ObjectPath clientObjectPath;
                Dictionary<string,Resource> resources;
                
                DataModel(string serverName)
                {
                        this.serverName = serverName;
                        this.resources = new Dictionary<string,Resource>();
                        this.client = new SimpleModelClient();
                        ConnectToBus();
                }

		public string ServerName
                {
                        get { return serverName; }
                }

                public bool Online
                {
                        get
                        {
                                // FIXME when dbus-sharp supports properties we can just say
                                // "model.Connected"
                                if (model != null)
                                        return (bool) model.Get("org.freedesktop.od.Model", "Connected");
                                else
                                        return false;
                        }
                }

                public List<Resource> Query(string methodName,
                                            string fetchString,
                                            Dictionary<string,string> properties)
                {
                        DBusResource[] results = model.Query(clientObjectPath,
                                                             methodName,
                                                             fetchString,
                                                             properties);
                        
                        List<Resource> resources = new List<Resource>();
                        foreach (DBusResource dbusResource in results)
                        {
                                UpdateResourceFromDBus(dbusResource);
                                
                                if (!dbusResource.Indirect)
                                {
                                        Resource resource = GetResource(dbusResource.ResourceId);
                                        if (resource == null)
                                                throw new Exception("Somehow failed to create a queried resource");
                                        resources.Add(resource);
                                }
                        }
                        
                        return resources;
                }

                public Resource QueryResource(string resourceId,
                                              string fetchString)
                {
                        Dictionary<string,string> properties = new Dictionary<string,string>();
                        properties.Add("resourceId", resourceId);
                        List<Resource> resources = Query("http://mugshot.org/p/system#getResource";,
                                                         fetchString, properties);
                        if (resources.Count != 1)
                                throw new Exception("Got wrong number of resources for query: " + resources.Count);

                        return resources[0];
                }
                
		private void ConnectToBus()
                {
                        Bus bus = Bus.Session;

                        string serverNameEscaped = "foo"; // FIXME

                        clientObjectPath =
                                new ObjectPath("/org/freedesktop/od/data_model_client/" + serverNameEscaped);
                        bus.Register(bus.UniqueName, // FIXME we should not need to pass in a bus name here
                                     clientObjectPath,
                                     client);

                        client.Notified += Notified;
                        
                        this.model = bus.GetObject<Model>("org.freedesktop.od.Engine",
                                                          new ObjectPath("/org/freedesktop/od/data_model"));

                        /* 
                        Dictionary<string,string> properties = new Dictionary<string,string>();
                        properties.Add("resourceId", "online-desktop:/o/global");
                        DBusResource[] results =
                                this.model.Query(clientObjectPath,
                                                 "http://mugshot.org/p/system#getResource";,
                                                 "onlineBuddies +",
                                                 properties);

                        foreach (DBusResource r in results)
                        {
                                Console.WriteLine("result: {0}", r.ResourceId);
                                foreach (DBusProperty p in r.Properties)
                                {
                                        Console.WriteLine(" p: {0} {1}", p.UnqualifiedName, p.Value);
                                }
                        }
*/
                }
                
                // called if we are notified of changes
                private void Notified(DBusResource[] resources)
                {
                        foreach (DBusResource r in resources)
                        {
                                Console.WriteLine(" notified of: {0}", r.ResourceId);
                                foreach (DBusProperty p in r.Properties)
                                {
                                        Console.WriteLine("  p: {0} {1}", p.UnqualifiedName, p.Value);
                                }

                                UpdateResourceFromDBus(r);
                        }
                }

                private Resource GetResource(string resourceId)
                {
                        Resource resource;
                        if (resources.TryGetValue(resourceId, out resource))
                                return resource;
                        else
                                return null;
                }

                private Resource EnsureResource(string resourceId, string classId)
                {
                        Resource resource;
                        if (resources.TryGetValue(resourceId, out resource))
                        {
                                return resource;
                        }
                        else
                        {
                                resource = new Resource(resourceId, classId);
                                resources.Add(resourceId, resource);
                                return resource;
                        }
                }

                private void UpdatePropertyFromDBus(Resource resource,
                                                    DBusProperty dbusProp)
                {
                        UpdateType updateType = (UpdateType) Enum.ToObject(typeof(UpdateType), dbusProp.UpdateType);

                        if (!Enum.IsDefined(typeof(UpdateType), updateType))
                                throw new Exception("Unknown update type " + dbusProp.UpdateType);

                        DataType dataType = (DataType) Enum.ToObject(typeof(DataType), dbusProp.DataType);

                        if (!Enum.IsDefined(typeof(DataType), dataType))
                                throw new Exception("Unknown data type " + dbusProp.DataType);
                        
                        Cardinality cardinality = (Cardinality) Enum.ToObject(typeof(Cardinality), dbusProp.Cardinality);
                        if (!Enum.IsDefined(typeof(Cardinality), cardinality))
                                throw new Exception("Unknown cardinality " + dbusProp.Cardinality);
                        

                        object value = dbusProp.Value;
                        
                        if (dataType == DataType.RESOURCE)
                        {
                                string resourceId = (string) value;
                                value = GetResource(resourceId);
                                if (value == null)
                                        throw new Exception("resource-valued element points to a resource we don't know about: " + resourceId);
                        }

                        resource.UpdateProperty(dbusProp.NamespaceUrl,
                                                dbusProp.UnqualifiedName,
                                                updateType,
                                                cardinality,
                                                value);
                }


                private void UpdateResourceFromDBus(DBusResource dbusResource)
                {
                        Resource resource = EnsureResource(dbusResource.ResourceId, dbusResource.ClassId);
                        foreach (DBusProperty dbusProp in dbusResource.Properties)
                        {
                                UpdatePropertyFromDBus(resource, dbusProp);
                        }
                }
        }

        // SimpleModelClient is an object for the data model
        // to call back to with notifications

        internal class SimpleModelClient : MarshalByRefObject, ModelClient
        {
                public void Notify(DBusResource[] resources)
                {
                        this.Notified(resources);
                }

                internal delegate void NotifiedHandler (DBusResource[] resources);
                internal event NotifiedHandler Notified;
        }        

        internal enum UpdateType
        {
                ADD = 'a',
                REPLACE = 'r',
                DELETE = 'd',
                CLEAR = 'c'
        }


        // the values here are as they appear in the dbus protocol,
        // but the enum is public since it's also used in public API
        public enum DataType
        {
                STRING = 's',
                RESOURCE = 'r',
                BOOLEAN = 'b',
                INTEGER = 'i',
                LONG = 'l',
                FLOAT = 'f',
                URL = 'u'
        }

        // the values here are as they appear in the dbus protocol,
        // but the enum is public since it's also used in public API        
        public enum Cardinality
        {
                ONE = '.',
                ZERO_OR_ONE = '?',
                N = '*'
        }
        
        // this class is used to store the resource values, eventually
        // we would allow creating nice interface proxies to it
        public class Resource
        {
                string resourceId;
                string classId;
                Dictionary<string,Dictionary<string,object>> properties;
                
                internal Resource(string resourceId,
                                  string classId)
                {
                        this.resourceId = resourceId;
                        this.classId = classId;
                        this.properties = new Dictionary<string,Dictionary<string,object>>();
                }
                
                public override string ToString()
                {
                        return ResourceId;
                }

                public delegate void ChangedHandler();
                public event ChangedHandler Changed;

                public string ResourceId
                {
                        get { return resourceId; }
                }

                public string ClassId
                {
                        get { return classId; }
                }

                public IEnumerable<string> GetPropertyNamespaces()
                {
                        return properties.Keys;
                }

                public IEnumerable<string> GetProperties(string namespaceUri)
                {
                        Dictionary<string,object> nsProps;
                        if (properties.TryGetValue(namespaceUri, out nsProps))
                        {
                                return nsProps.Keys;
                        }
                        else
                        {
                                // FIXME is there an emptyList() equivalent?
                                return new List<string>();
                        }
                }
                
                public object GetProperty(string namespaceUri,
                                          string unqualifiedName)
                {
                        Dictionary<string,object> nsProps;
                        if (properties.TryGetValue(namespaceUri, out nsProps))
                        {
                                object value;
                                if (nsProps.TryGetValue(unqualifiedName, out value))
                                {
                                        return value;
                                }
                        }

                        return null;
                }
                
                public object GetProperty(string unqualifiedName)
                {
                        foreach (Dictionary<string,object> nsProps in properties.Values)
                        {
                                object value;
                                if (nsProps.TryGetValue(unqualifiedName, out value))
                                {
                                        return value;
                                }
                        }
                        return null;
                }

                private void UpdateCardinalityOne(UpdateType updateType,
                                                  Dictionary<string,object> nsProps,
                                                  string unqualifiedName,
                                                  object value)
                {
                        switch (updateType)
                        {
                        case UpdateType.REPLACE:
                                // overwrites an old value if needed                                
                                nsProps[unqualifiedName] = value;
                                break;
                        case UpdateType.ADD:
                                // this throws if unqualifiedName already in the dict, which
                                // is what we want - it should not be there
                                nsProps.Add(unqualifiedName, value);
                                break;
                        case UpdateType.DELETE:
                                throw new Exception("DELETE not allowed with cardinality 1");
                        case UpdateType.CLEAR:
                                throw new Exception("CLEAR not allowed with cardinality 1");
                        }
                }

                private void UpdateCardinalityZeroOrOne(UpdateType updateType,
                                                        Dictionary<string,object> nsProps,
                                                        string unqualifiedName,
                                                        object value)
                {
                        switch (updateType)
                        {
                        case UpdateType.REPLACE:
                                // overwrites an old value if needed
                                nsProps[unqualifiedName] = value;
                                break;
                        case UpdateType.ADD:
                                // this throws if unqualifiedName already in the dict, which
                                // is what we want - it should not be there
                                nsProps.Add(unqualifiedName, value);                                
                                break;
                        case UpdateType.DELETE:
                                if (!nsProps.Remove(unqualifiedName))
                                        throw new Exception("property was DELETE'd but was not there");
                                break;
                        case UpdateType.CLEAR:
                                nsProps.Remove(unqualifiedName);
                                break;
                        }
                }

                private void UpdateCardinalityN(UpdateType updateType,
                                                Dictionary<string,object> nsProps,
                                                string unqualifiedName,
                                                object value)
                {
                        List<object> values;
                        
                        switch (updateType)
                        {
                        case UpdateType.REPLACE:
                                values = new List<object>();
                                nsProps[unqualifiedName] = values;
                                values.Add(value);
                                break;
                        case UpdateType.ADD:
                                if (nsProps.ContainsKey(unqualifiedName))
                                {
                                        values = (List<object>) nsProps[unqualifiedName];
                                }
                                else
                                {
                                        values = new List<object>();
                                        nsProps[unqualifiedName] = values;                                        
                                }
                                values.Add(value);
                                break;
                        case UpdateType.DELETE:
                                if (nsProps.ContainsKey(unqualifiedName))
                                {
                                        values = (List<object>) nsProps[unqualifiedName];
                                        values.Remove(value);
                                }
                                break;
                        case UpdateType.CLEAR:
                                if (nsProps.ContainsKey(unqualifiedName))
                                        nsProps[unqualifiedName] = new List<object>();
                                break;
                        }
                }

                
                internal void UpdateProperty(string namespaceUri,
                                             string unqualifiedName,
                                             UpdateType updateType,
                                             Cardinality cardinality,
                                             object value)
                {
                        Dictionary<string,object> nsProps;
                        if (!properties.TryGetValue(namespaceUri, out nsProps))
                        {
                                nsProps = new Dictionary<string,object>();
                                properties.Add(namespaceUri, nsProps);
                        }

                        if (updateType == UpdateType.DELETE)
                        {
                                if (!nsProps.ContainsKey(unqualifiedName))
                                        throw new Exception("Tried to DELETE a property we did not have");
                        }

                        switch (cardinality)
                        {
                        case Cardinality.ONE:
                                UpdateCardinalityOne(updateType, nsProps, unqualifiedName, value);
                                break;
                        case Cardinality.ZERO_OR_ONE:
                                UpdateCardinalityZeroOrOne(updateType, nsProps, unqualifiedName, value);
                                break;
                        case Cardinality.N:
                                UpdateCardinalityN(updateType, nsProps, unqualifiedName, value);
                                break;
                        }

                        
                        // notify changes (FIXME short-circuit if nothing really changed)
                        //Changed();
                }
        }
        
        // workaround for dbus-sharp mapping properties to a method
        // call rather than to this Properties interface
        [Interface ("org.freedesktop.DBus.Properties")]
        internal interface Properties : Introspectable
        {
                object Get(string interfaceName,
                           string propertyName);
                void   Set(string interfaceName,
                           string propertyName,
                           object value);
                Dictionary<string,object> GetAll(string interfaceName);
        }

        internal struct DBusProperty
        {
                internal string NamespaceUrl;
                internal string UnqualifiedName;
                internal byte UpdateType;
                internal byte DataType;
                internal byte Cardinality;
                internal object Value;
        }

        internal struct DBusResource
        {
                internal string ResourceId;
                internal string ClassId;
                internal bool Indirect;

                internal DBusProperty[] Properties;
        }

        [Interface ("org.freedesktop.od.ModelClient")]
        internal interface ModelClient
        {
                void Notify(DBusResource[] resources);
        }

        internal delegate void ConnectedChangedHandler (bool isConnected, string selfId);

        [Interface ("org.freedesktop.od.Model")]
        internal interface Model : Introspectable, Properties
        {
                // These properties don't work right with dbus-sharp right now, it
                // treats them as method call GetConnected rather than Properties.Get
/*
                bool Connected
                {
                        get;
                }

                string SelfId
                {
                        get;
                }
*/

                event ConnectedChangedHandler ConnectedChanged;

                DBusResource[] Query(ObjectPath client,
                                     string methodName,
                                     string fetchString,
                                     Dictionary<string,string> properties);

                void Update(string methodName,
                            Dictionary<string,string> properties);


                void Forget(ObjectPath clientToForget,
                            string resourceId);
        }

        public class TestDataModel
        {
                private static void WriteNSpaces(int count)
                {
                        while (count > 0)
                        {
                                Console.Write(" ");
                                --count;
                        }
                }
                
                private static void WriteResource(int indent, Resource r)
                {
                        WriteNSpaces(indent);
                        Console.WriteLine("resource {0}", r.ResourceId);
                        
                        foreach (string namespaceUri in r.GetPropertyNamespaces())
                        {
                                foreach (string prop in r.GetProperties(namespaceUri))
                                {
                                        object propValue = r.GetProperty(namespaceUri, prop);
                                        if (propValue is IEnumerable && !(propValue is string))
                                        {
                                                WriteNSpaces(indent);
                                                Console.Write("    {0} = [ ",
                                                              prop);
                                                foreach (object propElem in (IEnumerable) propValue)
                                                {
                                                        Console.Write("'{0}' ",
                                                                      propElem);
                                                }
                                                Console.WriteLine(" ]");

                                                foreach (object propElem in (IEnumerable) propValue)
                                                {
                                                        if (propElem is Resource)
                                                                WriteResource(indent + 4, (Resource) propElem);
                                                }
                                        }
                                        else
                                        {
                                                WriteNSpaces(indent);
                                                Console.WriteLine("    {0} = {1}",
                                                                  prop, propValue);
                                                if (propValue is Resource)
                                                {
                                                        WriteResource(indent + 4, (Resource) propValue);
                                                }
                                        }
                                }
                        }
                }
                
                public static void Main (string [] args)
                {
                        DataModel dm = DataModel.GetForServer("ignored right now");
                        Console.WriteLine("online: {0}", dm.Online);

                        Resource r = dm.QueryResource("online-desktop:/o/global", "onlineBuddies +");

                        WriteResource(0, r);
                        
                        Console.WriteLine("Done");
                }
        }
}


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