DataModel.cs
- From: Havoc Pennington <hp redhat com>
- To: GNOME Desktop Devel <desktop-devel-list gnome org>
- Subject: DataModel.cs
- Date: Tue, 07 Aug 2007 12:45:28 -0400
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]