r6864 - in dumbhippo/trunk/client: common/ddm common/hippo linux



Author: otaylor
Date: 2007-11-01 18:17:41 -0500 (Thu, 01 Nov 2007)
New Revision: 6864

Added:
   dumbhippo/trunk/client/common/ddm/ddm-client-notification.c
   dumbhippo/trunk/client/common/ddm/ddm-client-notification.h
   dumbhippo/trunk/client/common/ddm/ddm-client.c
   dumbhippo/trunk/client/common/ddm/ddm-client.h
   dumbhippo/trunk/client/common/ddm/ddm-local-client.c
   dumbhippo/trunk/client/common/ddm/ddm-local-client.h
   dumbhippo/trunk/client/common/ddm/ddm-work-item.c
   dumbhippo/trunk/client/common/ddm/ddm-work-item.h
   dumbhippo/trunk/client/common/ddm/test-local-data.xml
   dumbhippo/trunk/client/common/ddm/test-multipart-fetch.c
   dumbhippo/trunk/client/common/ddm/test-notification.c
Modified:
   dumbhippo/trunk/client/common/ddm/ddm-data-model-backend.h
   dumbhippo/trunk/client/common/ddm/ddm-data-model-dbus.c
   dumbhippo/trunk/client/common/ddm/ddm-data-model-internal.h
   dumbhippo/trunk/client/common/ddm/ddm-data-model.c
   dumbhippo/trunk/client/common/ddm/ddm-data-model.h
   dumbhippo/trunk/client/common/ddm/ddm-data-query-internal.h
   dumbhippo/trunk/client/common/ddm/ddm-data-query.c
   dumbhippo/trunk/client/common/ddm/ddm-data-query.h
   dumbhippo/trunk/client/common/ddm/ddm-data-resource-internal.h
   dumbhippo/trunk/client/common/ddm/ddm-data-resource.c
   dumbhippo/trunk/client/common/ddm/ddm-data-resource.h
   dumbhippo/trunk/client/common/ddm/ddm-notification-set.c
   dumbhippo/trunk/client/common/ddm/static-file-backend.c
   dumbhippo/trunk/client/common/ddm/static-file-backend.h
   dumbhippo/trunk/client/common/ddm/static-file-parser.c
   dumbhippo/trunk/client/common/ddm/test-static-file-backend.c
   dumbhippo/trunk/client/common/hippo/hippo-data-model-backend.c
   dumbhippo/trunk/client/linux/Makefile-libddm.am
Log:
* Implement multipart fetches; when we get some data from the server, we check
  to see if fetches traverse from that data, through local data, and back to other
  server data, and if so, go out and get that data.

* Start getting rid of DDMNotificationSet; instead, the data model tracks what
  resources have changed, and at idle handles creating notifications for all changed
  resources that need notification.

hippo-data-model-backend.c: Remove workaround for lack of multipart fetches

ddm-work-item.[ch]: Work items are queued tasks blocking on a complete fetch
ddm-client.[ch] ddm-local-client.[ch]: DDMClient object represents a data model consumer

static-file-backend.h static-file-parser.c: Allow loading local items from a file

test-multipart-fetch.c test-notify.c local-test-data.xml: Tests for new functionality


Added: dumbhippo/trunk/client/common/ddm/ddm-client-notification.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-client-notification.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-client-notification.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,93 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include "ddm-client-notification.h"
+#include "ddm-data-model-internal.h"
+#include "ddm-work-item.h"
+
+struct _DDMClientNotificationSet {
+    guint refcount;
+
+    DDMDataModel *model;
+    GHashTable *work_items;
+};
+
+DDMClientNotificationSet *
+_ddm_client_notification_set_new (DDMDataModel *model)
+{
+    DDMClientNotificationSet *notification_set;
+
+    g_return_val_if_fail(DDM_IS_DATA_MODEL(model), NULL);
+
+    notification_set = g_new0(DDMClientNotificationSet, 1);
+
+    notification_set->refcount = 1;
+    notification_set->model = model;
+    notification_set->work_items = g_hash_table_new_full(g_direct_hash, NULL,
+                                                         NULL,
+                                                         (GDestroyNotify)_ddm_work_item_unref);
+
+    return notification_set;
+}
+
+DDMClientNotificationSet *
+_ddm_client_notification_set_ref (DDMClientNotificationSet *notification_set)
+{
+    g_return_val_if_fail (notification_set != NULL, NULL);
+    g_return_val_if_fail (notification_set->refcount > 0, NULL);
+
+    notification_set->refcount++;
+  
+    return notification_set;
+}
+
+void
+_ddm_client_notification_set_unref (DDMClientNotificationSet *notification_set)
+{
+    g_return_if_fail (notification_set != NULL);
+    g_return_if_fail (notification_set->refcount > 0);
+
+    notification_set->refcount--;
+    if (notification_set->refcount == 0) {
+        g_hash_table_destroy(notification_set->work_items);
+        g_free(notification_set);
+    }
+}
+
+void
+_ddm_client_notification_set_add (DDMClientNotificationSet *notification_set,
+                                  DDMDataResource          *resource,
+                                  DDMClient                *client,
+                                  DDMDataFetch             *fetch,
+                                  GSList                   *changed_properties)
+{
+    DDMWorkItem *work_item;
+
+    g_return_if_fail (notification_set != NULL);
+
+    work_item = g_hash_table_lookup(notification_set->work_items, client);
+    if (work_item == NULL) {
+        work_item = _ddm_work_item_notify_client_new(notification_set->model, client);
+        g_hash_table_insert(notification_set->work_items, client, work_item);
+    }
+
+    _ddm_work_item_notify_client_add(work_item, resource, fetch, changed_properties);
+}
+
+static void
+add_work_item_foreach (gpointer key,
+                       gpointer value,
+                       gpointer data)
+{
+    DDMWorkItem *work_item = value;
+    DDMClientNotificationSet *notification_set = data;
+
+    _ddm_data_model_add_work_item(notification_set->model, work_item);
+}
+
+void
+_ddm_client_notification_set_add_work_items (DDMClientNotificationSet *notification_set)
+{
+    g_hash_table_foreach(notification_set->work_items, add_work_item_foreach, notification_set);
+    g_hash_table_remove_all(notification_set->work_items);
+    
+}

Added: dumbhippo/trunk/client/common/ddm/ddm-client-notification.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-client-notification.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-client-notification.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#ifndef __DDM_CLIENT_NOTIFICATION_H__
+#define __DDM_CLIENT_NOTIFICATION_H__
+
+#include "ddm-client.h"
+#include "ddm-data-model.h"
+#include "ddm-data-resource.h"
+
+G_BEGIN_DECLS
+
+/* A "client notification set" is used to track notifications to send
+ * out to the different clients. At idle, we iterate over all
+ * resources that have changed and add them to the client notification
+ * set using _ddm_data_resource_resolve_notifications(). This creates
+ * a work item for each client.
+ *
+ * We then add the work items to the queue, the standard process runs
+ * to make sure that we have all fetches complete we need to send out
+ * the notification, and when the fetches are complete, we fire the
+ * notification on the DDDClient.
+ */
+
+typedef struct _DDMClientNotificationSet DDMClientNotificationSet;
+
+DDMClientNotificationSet *_ddm_client_notification_set_new (DDMDataModel *model);
+DDMClientNotificationSet *_ddm_client_notification_set_ref (DDMClientNotificationSet *notification_set);
+
+void _ddm_client_notification_set_unref (DDMClientNotificationSet *notification_set);
+
+void _ddm_client_notification_set_add (DDMClientNotificationSet *notification_set,
+				       DDMDataResource          *resource,
+				       DDMClient                *client,
+				       DDMDataFetch             *fetch,
+				       GSList                   *changed_properties);
+
+void _ddm_client_notification_set_add_work_items (DDMClientNotificationSet *notification_set);
+
+G_END_DECLS
+
+#endif /* __DDM_CLIENT_NOTIFICATION_H__ */
+

Added: dumbhippo/trunk/client/common/ddm/ddm-client.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-client.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-client.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,44 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include "ddm-client.h"
+
+GType
+ddm_client_get_type (void)
+{
+    static GType type = 0;
+
+    if (!type)
+        type = g_type_register_static_simple(G_TYPE_INTERFACE, "DDMClient",
+                                             sizeof (DDMClientIface),
+                                             NULL, 0, NULL, 0);
+    
+    return type;
+}
+
+gpointer
+ddm_client_begin_notification (DDMClient *client)
+{
+    g_return_val_if_fail(DDM_IS_CLIENT(client), NULL);
+
+    return DDM_CLIENT_GET_IFACE(client)->begin_notification(client);
+}
+
+void
+ddm_client_notify (DDMClient       *client,
+		   DDMDataResource *resource,
+		   GSList          *changed_properties,
+		   gpointer         notification_data)
+{
+    g_return_if_fail(DDM_IS_CLIENT(client));
+
+    DDM_CLIENT_GET_IFACE(client)->notify(client, resource, changed_properties, notification_data);
+}
+
+void
+ddm_client_end_notification (DDMClient       *client,
+			     gpointer         notification_data)
+{
+    g_return_if_fail(DDM_IS_CLIENT(client));
+
+    DDM_CLIENT_GET_IFACE(client)->end_notification(client, notification_data);
+}

Added: dumbhippo/trunk/client/common/ddm/ddm-client.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-client.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-client.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,64 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#ifndef DDM_COMPILATION
+#ifndef DDM_INSIDE_DDM_H
+#error "Do not include this file directly, include ddm.h instead"
+#endif /* DDM_INSIDE_DDM_H */
+#endif /* DDM_COMPILATION */
+
+#ifndef __DDM_CLIENT_H__
+#define __DDM_CLIENT_H__
+
+#include <ddm/ddm-data-resource.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* DDMClient represents a consumer of the data model; there is one DDMClient
+ * (a DDMLocalClient) used to represent use of the data model directly from
+ * within the same process. A more typical use of DDMClient would be to represent
+ * another application consuming the data model via D-BUS.
+ */
+
+typedef struct _DDMClientIface DDMClientIface;
+
+#define DDM_TYPE_CLIENT              (ddm_client_get_type ())
+#define DDM_CLIENT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), DDM_TYPE_CLIENT, DDMClient))
+#define DDM_IS_CLIENT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), DDM_TYPE_CLIENT))
+#define DDM_CLIENT_GET_IFACE(obj)    (G_TYPE_INSTANCE_GET_INTERFACE ((obj), DDM_TYPE_CLIENT, DDMClientIface))
+
+struct _DDMClientIface
+{
+    GTypeInterface parent_iface;
+
+    /* Start notification; the returned gpointer is sent to all notify()
+     * calls that are part of the process and to end_notification()
+     */
+    gpointer (* begin_notification) (DDMClient       *client);
+
+    /* Called for each resource that has changed
+     */
+    void     (* notify)             (DDMClient       *client,
+                                     DDMDataResource *resource,
+                                     GSList          *changed_properties,
+                                     gpointer         notification_data);
+
+    /* This notification is all done, send it off
+     */
+    void     (* end_notification)   (DDMClient       *client,
+                                     gpointer         notification_data);
+};
+
+GType ddm_client_get_type(void) G_GNUC_CONST;
+
+gpointer ddm_client_begin_notification (DDMClient       *client);
+void     ddm_client_notify             (DDMClient       *client,
+                                        DDMDataResource *resource,
+                                        GSList          *changed_properties,
+                                        gpointer         notification_data);
+void     ddm_client_end_notification   (DDMClient       *client,
+                                        gpointer         notification_data);
+
+G_END_DECLS
+
+#endif /* __DDM_CLIENT_H__ */

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-model-backend.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-model-backend.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-model-backend.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -28,6 +28,10 @@
                                  DDMDataQuery *query,
                                  void         *backend_data);
 
+    /* Do idle processing; at the beginning of ddm_data_model_flush */
+    void     (* flush)          (DDMDataModel *model,
+                                 void         *backend_data);
+
     GCallback _ddm_padding_1;
     GCallback _ddm_padding_2;
     GCallback _ddm_padding_3;
@@ -35,7 +39,6 @@
     GCallback _ddm_padding_5;
     GCallback _ddm_padding_6;
     GCallback _ddm_padding_7;
-    GCallback _ddm_padding_8;
 };
 
 G_END_DECLS

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-model-dbus.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-model-dbus.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-model-dbus.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -403,7 +403,7 @@
         dbus_message_iter_next(&prop_array_iter);
     }
     
-    return resource;
+    return indirect ? NULL : resource;
 }
 
 /* query_to_respond_to is NULL for a notification */
@@ -444,7 +444,7 @@
         
         resource = handle_incoming_resource_update(dbus_model, &resource_struct_iter, notifications);
 
-        if (query_to_respond_to)
+        if (query_to_respond_to && resource != NULL)
             resources = g_slist_prepend(resources, resource);
 
         dbus_message_iter_next(&resource_array_iter);

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-model-internal.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-model-internal.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-model-internal.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -4,9 +4,23 @@
 
 #include "ddm-data-model.h"
 #include "ddm-data-resource.h"
+#include "ddm-work-item.h"
 
 G_BEGIN_DECLS
 
+void _ddm_data_model_mark_changed   (DDMDataModel    *model,
+                                     DDMDataResource *resource);
+void _ddm_data_model_add_work_item  (DDMDataModel    *model,
+                                     DDMWorkItem     *item);
+void _ddm_data_model_query_answered (DDMDataModel    *model,
+                                     DDMDataQuery    *query);
+
+DDMDataQuery *_ddm_data_model_query_remote_resource (DDMDataModel *model,
+                                                     const char     *resource_id,
+                                                     const char     *fetch);
+
+DDMClient *_ddm_data_model_get_local_client (DDMDataModel *model);
+
 G_END_DECLS
 
 #endif /* __DDM_DATA_MODEL_INTERNAL__H__ */

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-model.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-model.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-model.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -9,6 +9,7 @@
 #include "ddm-data-model-backend.h"
 #include "ddm-data-resource-internal.h"
 #include "ddm-data-query-internal.h"
+#include "ddm-local-client.h"
 
 static void      ddm_data_model_init                (DDMDataModel       *model);
 static void      ddm_data_model_class_init          (DDMDataModelClass  *klass);
@@ -20,11 +21,21 @@
     GObject parent;
 
     const DDMDataModelBackend *backend;
-    void                      *backend_data;
-    GFreeFunc                  free_backend_data_func;
+    void *backend_data;
+    GFreeFunc free_backend_data_func;
+
+    DDMClient *local_client;
     
     GHashTable *resources;
+    GHashTable *changed_resources;
 
+    GQueue *work_items;
+
+    gint64 next_query_serial;
+    gint64 max_answered_query_serial;
+
+    guint flush_idle;
+
     guint connected : 1;
 };
 
@@ -45,6 +56,12 @@
 ddm_data_model_init(DDMDataModel *model)
 {
     model->resources = g_hash_table_new(g_str_hash, g_str_equal);
+    model->changed_resources = g_hash_table_new(g_direct_hash, NULL);
+    model->work_items = g_queue_new();
+
+    model->max_answered_query_serial = -1;
+
+    model->local_client = _ddm_local_client_new(model);
 }
 
 static void
@@ -78,17 +95,27 @@
         model->backend_data = NULL;
         model->free_backend_data_func = NULL;
     }
-    
+
     G_OBJECT_CLASS(ddm_data_model_parent_class)->dispose(object);
 }
 
 static void
 ddm_data_model_finalize(GObject *object)
 {
-#if 0
     DDMDataModel *model = DDM_DATA_MODEL(object);
-#endif
 
+    if (model->flush_idle != 0)
+        g_source_remove(model->flush_idle);
+
+    /* FIXME: cancel and remove everything still in here */
+    g_queue_free(model->work_items);
+    
+    /* FIXME: actually clean up the resources */
+    g_hash_table_destroy(model->resources);
+    g_hash_table_destroy(model->changed_resources);
+
+    g_object_unref(model->local_client);
+
     G_OBJECT_CLASS(ddm_data_model_parent_class)->finalize(object);
 }
 
@@ -153,12 +180,38 @@
     return params;
 }
 
-DDMDataQuery *
-ddm_data_model_query_params(DDMDataModel *model,
-                            const char   *method,
-                            const char   *fetch,
-                            GHashTable   *params)
+static void
+dump_param_foreach(gpointer k,
+                   gpointer v,
+                   gpointer data)
 {
+    DDMDataQuery *query = data;
+    const char *key = k;
+    const char *value = v;
+    const char *id_string = ddm_data_query_get_id_string(query);
+    
+    g_debug("%s: %s='%s'", id_string, key, value);
+}
+
+static void
+debug_dump_query(DDMDataQuery *query)
+{
+    const char *id_string = ddm_data_query_get_id_string(query);
+    DDMQName *qname = ddm_data_query_get_qname(query);
+    
+    g_debug("%s: Sending to server", id_string);
+    g_debug("%s: uri=%s#%s", id_string, qname->uri, qname->name);
+    g_debug("%s: fetch=%s", id_string, ddm_data_query_get_fetch_string(query));
+    g_hash_table_foreach(ddm_data_query_get_params(query), dump_param_foreach, query);
+}
+
+static DDMDataQuery *
+data_model_query_params_internal(DDMDataModel *model,
+                                 const char   *method,
+                                 const char   *fetch,
+                                 GHashTable   *params,
+                                 gboolean      force_remote)
+{
     DDMDataQuery *query;
     DDMQName *method_qname;
 
@@ -169,13 +222,61 @@
     if (method_qname == NULL)
         return NULL;
 
-    query = _ddm_data_query_new(model, method_qname, fetch, params);
+    query = _ddm_data_query_new(model, method_qname, fetch, params, model->next_query_serial++);
+    if (query == NULL) /* Bad fetch string */
+        return NULL;
 
+    if (!force_remote && method_qname == ddm_qname_get("http://mugshot.org/p/system";, "getResource")) {
+        const char *resource_id = g_hash_table_lookup(params, "resourceId");
+        DDMDataResource *resource;
+        GSList *results;
+        
+        if (resource_id == NULL) {
+            /* FIXME: ERROR ASYNC HERE */
+            return NULL;
+        }
+
+        /* The reason why we ensure (without specifying a class_id) here is so that
+         * we can record the fetches as they go out and avoid sending duplicate
+         * fetches to the server even if we've never fetched anything for the
+         * resource before.
+         *
+         * The actual fetch will be done when we process the response work item
+         * and find out what we are missing.
+         *
+         * We do the lookup first to avoid complaints about calling ensure_resource()
+         * on a local resource.
+         */
+        resource = ddm_data_model_lookup_resource(model, resource_id);
+        if (resource == NULL)
+            resource = ddm_data_model_ensure_resource(model, resource_id, NULL);
+        results = g_slist_prepend(NULL, resource);
+
+        _ddm_data_query_local_response(query, results);
+        
+        g_slist_free(results);
+        
+        return query;
+    }
+    
+    debug_dump_query(query);
     model->backend->send_query(model, query, model->backend_data);
     return query;
 }
 
 DDMDataQuery *
+ddm_data_model_query_params(DDMDataModel *model,
+                            const char   *method,
+                            const char   *fetch,
+                            GHashTable   *params)
+{
+    g_return_val_if_fail (DDM_IS_DATA_MODEL(model), NULL);
+    g_return_val_if_fail (model->backend != NULL, NULL);
+
+    return data_model_query_params_internal(model, method, fetch, params, FALSE);
+}
+
+DDMDataQuery *
 ddm_data_model_query(DDMDataModel *model,
                      const char     *method,
                      const char     *fetch,
@@ -196,6 +297,28 @@
     return query;
 }
 
+static DDMDataQuery *
+data_model_query_internal(DDMDataModel *model,
+                          const char   *method,
+                          const char   *fetch,
+                          gboolean      force_remote,
+                          ...)
+{
+    DDMDataQuery *query;
+    GHashTable *params;
+    va_list vap;
+
+    va_start(vap, force_remote);
+    params = params_from_valist(vap);
+    va_end(vap);
+
+    query = data_model_query_params_internal(model, method, fetch, params, force_remote);
+
+    g_hash_table_destroy(params);
+
+    return query;
+}
+
 DDMDataQuery *
 ddm_data_model_query_resource(DDMDataModel *model,
                               const char     *resource_id,
@@ -203,12 +326,24 @@
 {
     g_return_val_if_fail (DDM_IS_DATA_MODEL(model), NULL);
 
-    return ddm_data_model_query(model, "http://mugshot.org/p/system#getResource";, fetch,
-                                "resourceId", resource_id,
-                                NULL);
+    return data_model_query_internal(model, "http://mugshot.org/p/system#getResource";, fetch, FALSE,
+                                     "resourceId", resource_id,
+                                     NULL);
 }
 
 DDMDataQuery *
+_ddm_data_model_query_remote_resource(DDMDataModel *model,
+                                      const char   *resource_id,
+                                      const char   *fetch)
+{
+    g_return_val_if_fail (DDM_IS_DATA_MODEL(model), NULL);
+
+    return data_model_query_internal(model, "http://mugshot.org/p/system#getResource";, fetch, TRUE,
+                                     "resourceId", resource_id,
+                                     NULL);
+}
+
+DDMDataQuery *
 ddm_data_model_update_params(DDMDataModel *model,
                              const char   *method,
                              GHashTable   *params)
@@ -223,10 +358,10 @@
     if (method_qname == NULL) /* Invalid method URI */
         return NULL;
 
-    query = _ddm_data_query_new_update(model, method_qname, params);
-    if (query == NULL) /* Bad fetch string */
-        return NULL;
+    query = _ddm_data_query_new_update(model, method_qname, params, model->next_query_serial++);
 
+    debug_dump_query(query);
+    
     model->backend->send_update(model, query, model->backend_data);
     
     return query;
@@ -271,20 +406,24 @@
 
     resource = g_hash_table_lookup(model->resources, resource_id);
     if (resource) {
-        if ((local != FALSE) != ddm_data_resource_get_local(resource)) {
+        if ((local != FALSE) != ddm_data_resource_is_local(resource)) {
             g_warning("Mismatch for 'local' nature of resource '%s', old=%d, new=%d",
                       resource_id, !local, local);
         }
 
         if (class_id) {
             const char *old_class_id = ddm_data_resource_get_class_id(resource);
-            if (old_class_id && strcmp(class_id, old_class_id) != 0)
-                g_warning("Mismatch for class_id of resource '%s', old=%s, new=%s",
-                          resource_id, old_class_id, class_id);
+            if (old_class_id) {
+                if (strcmp(class_id, old_class_id) != 0)
+                    g_warning("Mismatch for class_id of resonurce '%s', old=%s, new=%s",
+                              resource_id, old_class_id, class_id);
+            } else {
+                ddm_data_resource_set_class_id(resource, class_id);
+            }
         }
 
     } else {
-        resource = _ddm_data_resource_new(resource_id, class_id, local);
+        resource = _ddm_data_resource_new(model, resource_id, class_id, local);
         g_hash_table_insert(model->resources, (char *)ddm_data_resource_get_resource_id(resource), resource);
     }
 
@@ -318,3 +457,209 @@
     model->connected = connected;
     g_signal_emit(G_OBJECT(model), signals[CONNECTED_CHANGED], 0, connected);
 }
+
+void
+_ddm_data_model_mark_changed(DDMDataModel    *model,
+                             DDMDataResource *resource)
+{
+    if (g_hash_table_lookup(model->changed_resources, resource) == NULL) {
+        g_hash_table_insert(model->changed_resources, resource, resource);
+    }
+
+    ddm_data_model_schedule_flush(model);
+}
+
+static gboolean
+do_flush(gpointer data)
+{
+    ddm_data_model_flush(data);
+
+    return FALSE;
+}
+
+void
+ddm_data_model_schedule_flush (DDMDataModel *model)
+{
+    if (model->flush_idle == 0)
+        model->flush_idle = g_idle_add(do_flush, model);
+}
+
+static int
+compare_work_items(gconstpointer  a,
+                   gconstpointer  b,
+                   gpointer       data)
+{
+    const DDMWorkItem *item_a = a;
+    const DDMWorkItem *item_b = b;
+
+    gint64 serial_a = _ddm_work_item_get_min_serial(item_a);
+    gint64 serial_b = _ddm_work_item_get_min_serial(item_b);
+
+    return (serial_a < serial_b) ? -1 : (serial_a == serial_b ? 0 : 1);
+}
+
+void
+_ddm_data_model_add_work_item (DDMDataModel    *model,
+                               DDMWorkItem     *item)
+{
+    GList *l;
+    
+    _ddm_work_item_ref(item);
+
+    /* Two situations:
+     *
+     * We have a new item with min_serial = -1; it probably goes right
+     * near the beginning of the queue.
+     *
+     * We have an item that now has a min_serial because we've sent a
+     * request to the server; it goes at the end of the queue.
+     *
+     * Slightly over-optimize here by handling the two cases separately.
+     * Note that both cases are different from g_queue_insert_sorted()
+     * because we insert after on the tie-break to avoid reordering
+     * when we pull things off the front.
+     */
+    if (_ddm_work_item_get_min_serial(item) == -1) {
+        for (l = model->work_items->head; l; l = l->next) {
+            if (compare_work_items(l->data, item, NULL) > 0)
+                break;
+        }
+
+        if (l == NULL)
+            g_queue_push_tail(model->work_items, item);
+        else
+            g_queue_insert_before(model->work_items, l, item);
+        
+    } else {
+        for (l = model->work_items->tail; l; l = l->prev) {
+            if (compare_work_items(l->data, item, NULL) <= 0)
+                break;
+        }
+        
+        if (l == NULL)
+            g_queue_push_head(model->work_items, item);
+        else
+            g_queue_insert_after(model->work_items, l, item);
+    }
+    
+
+    ddm_data_model_schedule_flush(model);
+}
+
+gboolean
+ddm_data_model_needs_flush(DDMDataModel *model)
+{
+    return model->flush_idle != 0;
+}
+
+static void
+flush_notifications_foreach(gpointer key,
+                            gpointer value,
+                            gpointer data)
+{
+    DDMDataResource *resource = key;
+    DDMClientNotificationSet *notification_set = data;
+
+    _ddm_data_resource_resolve_notifications(resource, notification_set);
+}
+
+static void
+data_model_flush_notifications(DDMDataModel *model)
+{
+    DDMClientNotificationSet *notification_set;
+    
+    if (g_hash_table_size(model->changed_resources) == 0)
+        return;
+
+    notification_set = _ddm_client_notification_set_new(model);
+
+    g_hash_table_foreach(model->changed_resources, flush_notifications_foreach, notification_set);
+    g_hash_table_remove_all(model->changed_resources);
+
+    _ddm_client_notification_set_add_work_items(notification_set);
+    _ddm_client_notification_set_unref(notification_set);
+}
+
+static void
+data_model_flush_work_items(DDMDataModel *model)
+{
+    GList *items;
+    GList *l;
+    int count = 0;
+    
+    /* Find the work items that might possibly be ready to process */
+    count = 0;
+    for (l = model->work_items->head; l; l = l->next) {
+        DDMWorkItem *item = l->data;
+
+        if (_ddm_work_item_get_min_serial(item) > model->max_answered_query_serial)
+            break;
+
+        count++;
+    }
+
+    /* And chop them out of the list of pending items */
+    if (l == model->work_items->head) {
+        return;
+    } else if (l == NULL) {
+        items = model->work_items->head;
+        model->work_items->head = NULL;
+        model->work_items->tail = NULL;
+        model->work_items->length = 0;
+    } else {
+        l->prev->next = NULL;
+        l->prev = NULL;
+        
+        items = model->work_items->head;
+        model->work_items->head = l;
+        model->work_items->length -= count;
+    }
+
+    /* Now walk through the possibly ready items, process them and insert them
+     * back into the list if they are still not ready.
+     */
+    for (l = items; l; l = l->next) {
+        DDMWorkItem *item = l->data;
+
+        if (!_ddm_work_item_process(item))
+            _ddm_data_model_add_work_item(model, item);
+
+        _ddm_work_item_unref(item);
+    }
+
+    g_list_free(items);
+}
+
+void
+ddm_data_model_flush(DDMDataModel *model)
+{
+    if (model->flush_idle == 0)
+        return;
+
+    g_debug("Flushing Data Model");
+
+    g_source_remove(model->flush_idle);
+    model->flush_idle = 0;
+
+    if (model->backend->flush)
+        model->backend->flush(model, model->backend_data);
+
+    data_model_flush_notifications(model);
+    data_model_flush_work_items(model);
+}
+
+void
+_ddm_data_model_query_answered (DDMDataModel *model,
+                                DDMDataQuery *query)
+{
+    gint64 serial = _ddm_data_query_get_serial(query);
+    if (serial > model->max_answered_query_serial)
+        model->max_answered_query_serial = serial;
+}
+
+DDMClient *
+_ddm_data_model_get_local_client (DDMDataModel *model)
+{
+    return model->local_client;
+}
+

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-model.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-model.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-model.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -72,10 +72,16 @@
                                                        const char     *resource_id,
                                                        const char     *class_id);
 
+/* Do all processing normally done on idle */
+
+gboolean ddm_data_model_needs_flush (DDMDataModel *model);
+void     ddm_data_model_flush       (DDMDataModel *model);
+
 /* should only be called by backends */
-void          ddm_data_model_set_connected      (DDMDataModel   *model,
-                                                 gboolean        connected);
 
+void ddm_data_model_schedule_flush (DDMDataModel *model);
+void ddm_data_model_set_connected  (DDMDataModel *model,
+                                    gboolean      connected);
 
 G_END_DECLS
 

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-query-internal.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-query-internal.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-query-internal.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -10,12 +10,24 @@
 DDMDataQuery *_ddm_data_query_new(DDMDataModel   *model,
                                   DDMQName       *qname,
                                   const char     *fetch_string,
-                                  GHashTable     *params);
+                                  GHashTable     *params,
+                                  gint64          serial);
 
 DDMDataQuery *_ddm_data_query_new_update(DDMDataModel   *model,
                                          DDMQName       *qname,
-                                         GHashTable     *params);
+                                         GHashTable     *params,
+                                         gint64          serial);
 
+/* Called when we short-circuit a getResource response and throw
+ * it immediately on the work-item pile
+ */
+void _ddm_data_query_local_response (DDMDataQuery *query,
+                                     GSList       *results);
+
+void _ddm_data_query_run_response (DDMDataQuery *query);
+
+gint64 _ddm_data_query_get_serial (DDMDataQuery *query);
+
 G_END_DECLS
 
 #endif /* __DDM_DATA_QUERY_INTERNAL_H__ */

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-query.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-query.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-query.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -1,6 +1,8 @@
 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
 
 #include "ddm-data-query-internal.h"
+#include "ddm-data-model-internal.h"
+#include "ddm-data-resource-internal.h"
 
 typedef enum {
     HANDLER_NONE,
@@ -12,10 +14,12 @@
 struct _DDMDataQuery {
     DDMDataModel *model;
     DDMQName *qname;
+    gint64 serial;
     gboolean is_update;
     char *fetch_string;
     DDMDataFetch *fetch;
     GHashTable *params;
+    GSList *results;
 
     HandlerType handler_type;
     union {
@@ -26,6 +30,7 @@
     gpointer handler_data;
     DDMErrorHandler error_handler;
     gpointer error_handler_data;
+    char *id_string;
 };
 
 DDMDataModel *
@@ -76,6 +81,12 @@
     return query->params;
 }
 
+GSList *
+ddm_data_query_get_results (DDMDataQuery *query)
+{
+    return query->results;
+}
+
 void
 ddm_data_query_set_single_handler (DDMDataQuery     *query,
                                    DDMSingleHandler  handler,
@@ -139,7 +150,8 @@
 _ddm_data_query_new (DDMDataModel *model,
                      DDMQName     *qname,
                      const char   *fetch_string,
-                     GHashTable   *params)
+                     GHashTable   *params,
+                     gint64        serial)
 {
     DDMDataQuery *query;
     DDMDataFetch *fetch;
@@ -165,14 +177,18 @@
     g_hash_table_foreach(params, add_param_foreach, query);
 
     query->handler_type = HANDLER_NONE;
+    query->serial = serial;
 
+    query->id_string = g_strdup_printf("Query-%" G_GINT64_MODIFIER "d", serial);
+
     return query;
 }
 
 DDMDataQuery *
 _ddm_data_query_new_update (DDMDataModel *model,
                             DDMQName     *qname,
-                            GHashTable   *params)
+                            GHashTable   *params,
+                            gint64        serial)
 {
     DDMDataQuery *query =  g_new0(DDMDataQuery, 1);
 
@@ -186,7 +202,10 @@
     g_hash_table_foreach(params, add_param_foreach, query);
 
     query->handler_type = HANDLER_NONE;
+    query->serial = serial;
 
+    query->id_string = g_strdup_printf("Update-%" G_GINT64_MODIFIER "d", serial);
+
     return query;
 }
 
@@ -198,38 +217,138 @@
     
     g_free(query->fetch_string);
     g_hash_table_destroy(query->params);
+
+    g_slist_free(query->results);
+    g_free(query->id_string);
+    
     g_free(query);
 }
 
+static void
+mark_received_fetches(DDMDataResource *resource,
+                      DDMDataFetch    *fetch,
+                      gboolean         local)
+{
+    DDMDataFetchIter iter;
+
+    /* For a fetch we short-circuit locally, we don't need to record
+     * record any additional fetches on remote resources ... we can
+     * only short-circuit a remote fetch if we already have everything
+     * we need. But for a local resource (injected into the data model),
+     * we want to record our fetch in resource->received_fetch, because
+     * we use resource->received_fetch when calculating local interest.
+     *
+     * See comment in _ddm_data_resource_resolve_notification()
+     */
+    if (!local || ddm_data_resource_is_local(resource))
+        _ddm_data_resource_fetch_received(resource, fetch);
+
+    ddm_data_fetch_iter_init(&iter, resource, fetch);
+
+    while (ddm_data_fetch_iter_has_next(&iter)) {
+        DDMDataProperty *property;
+        DDMDataFetch *children;
+        DDMDataValue value;
+        
+        ddm_data_fetch_iter_next(&iter, &property, &children);
+
+        if (children != NULL) {
+            ddm_data_property_get_value(property, &value);
+            
+            g_assert (DDM_DATA_BASE(value.type) == DDM_DATA_RESOURCE);
+            
+            if (DDM_DATA_IS_LIST(value.type)) {
+                GSList *l;
+                
+                for (l = value.u.list; l; l = l->next) {
+                    mark_received_fetches(l->data, children, local);
+                }
+            } else {
+                mark_received_fetches(value.u.resource, children, local);
+            }
+        }
+    }
+    
+    ddm_data_fetch_iter_clear(&iter);
+}
+
+static void
+data_query_response_internal (DDMDataQuery *query,
+                              GSList       *results,
+                              gboolean      local)
+{
+    DDMWorkItem *item;
+    GSList *l;
+
+    if (!local) {
+        g_debug("%s: Received response", query->id_string);
+        
+        for (l = results; l; l = l->next) {
+            g_debug("%s: result=%s", query->id_string, ddm_data_resource_get_resource_id(l->data));
+        }
+    }
+        
+    for (l = results; l; l = l->next) {
+        mark_received_fetches(l->data, query->fetch, local);
+    }
+
+    _ddm_data_model_query_answered(query->model, query);
+    
+    query->results = g_slist_copy(results);
+
+    item = _ddm_work_item_query_response_new(query->model, query);
+    _ddm_data_model_add_work_item(query->model, item);
+    _ddm_work_item_unref(item);
+}
+
 void
-ddm_data_query_response (DDMDataQuery  *query,
-                         GSList        *results)
+ddm_data_query_response (DDMDataQuery *query,
+                         GSList       *results)
 {
     g_return_if_fail(query != NULL);
+    
+    data_query_response_internal(query, results, FALSE);
+}
 
+void
+_ddm_data_query_local_response (DDMDataQuery *query,
+                                GSList       *results)
+{
+    g_return_if_fail(query != NULL);
+    
+    data_query_response_internal(query, results, TRUE);
+}
+
+void
+_ddm_data_query_run_response (DDMDataQuery *query)
+{
+    g_return_if_fail(query != NULL);
+
+    g_debug("%s: Have complete fetch, running response", query->id_string);
+    
     switch (query->handler_type) {
     case HANDLER_NONE:
         return;
     case HANDLER_SINGLE:
-        if (results == NULL) {
+        if (query->results == NULL) {
             ddm_data_query_error(query,
                                  DDM_DATA_ERROR_ITEM_NOT_FOUND,
                                  "No result for a query expecting a single result");
             return;
         }
-        if (g_slist_length(results) > 1) {
+        if (g_slist_length(query->results) > 1) {
             ddm_data_query_error(query,
                                  DDM_DATA_ERROR_BAD_REPLY,
                                  "Too many results for a query expecting a single result");
             return;
         }
-        query->handler.single(results->data, query->handler_data);
+        query->handler.single(query->results->data, query->handler_data);
         break;
     case HANDLER_MULTI:
-        query->handler.multi(results, query->handler_data);
+        query->handler.multi(query->results, query->handler_data);
         break;
     case HANDLER_UPDATE:
-        if (results != NULL) {
+        if (query->results != NULL) {
             ddm_data_query_error(query,
                                  DDM_DATA_ERROR_BAD_REPLY,
                                  "Got results for a query expecting no results");
@@ -243,14 +362,30 @@
 }
 
 void
-ddm_data_query_error (DDMDataQuery  *query,
-                      DDMDataError   error,
-                      const char    *message)
+ddm_data_query_error (DDMDataQuery *query,
+                      DDMDataError  error,
+                      const char   *message)
 {
     g_return_if_fail(query != NULL);
 
+    g_debug("%s: Got error response: %s (%d)", query->id_string, message != NULL ? message : "<null>", error);
+
+    _ddm_data_model_query_answered(query->model, query);
+    
     if (query->error_handler)    
         query->error_handler(error, message, query->error_handler_data);
     
     ddm_data_query_free(query);
 }
+
+gint64
+_ddm_data_query_get_serial (DDMDataQuery *query)
+{
+    return query->serial;
+}
+
+const char *
+ddm_data_query_get_id_string (DDMDataQuery *query)
+{
+    return query->id_string;
+}

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-query.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-query.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-query.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -25,11 +25,8 @@
                                   const char        *message,
                                   gpointer           user_data);
 
-DDMDataModel *ddm_data_query_get_model          (DDMDataQuery     *query);
-DDMQName *    ddm_data_query_get_qname          (DDMDataQuery     *query);
-const char *  ddm_data_query_get_fetch_string   (DDMDataQuery     *query);
-DDMDataFetch *ddm_data_query_get_fetch          (DDMDataQuery     *query);
-gboolean      ddm_data_query_is_update          (DDMDataQuery     *query);
+/******* For applications *******/
+
 void          ddm_data_query_set_single_handler (DDMDataQuery     *query,
                                                  DDMSingleHandler  handler,
                                                  gpointer          user_data);
@@ -43,15 +40,27 @@
                                                  DDMErrorHandler   handler,
                                                  gpointer          user_data);
 
-GHashTable *ddm_data_query_get_params (DDMDataQuery *query);
-void        ddm_data_query_response   (DDMDataQuery *query,
-                                       GSList       *results);
-void        ddm_data_query_error      (DDMDataQuery *query,
-                                       DDMDataError  error,
-                                       const char   *message);
+/***** For data model backends ******/
 
+DDMDataModel *ddm_data_query_get_model        (DDMDataQuery *query);
+DDMQName *    ddm_data_query_get_qname        (DDMDataQuery *query);
+const char *  ddm_data_query_get_fetch_string (DDMDataQuery *query);
+DDMDataFetch *ddm_data_query_get_fetch        (DDMDataQuery *query);
+gboolean      ddm_data_query_is_update        (DDMDataQuery *query);
+GHashTable *  ddm_data_query_get_params       (DDMDataQuery *query);
+GSList *      ddm_data_query_get_results      (DDMDataQuery *query);
 
+/* Called by the backend when a response is received from upstream */
 
+void ddm_data_query_response (DDMDataQuery *query,
+                              GSList       *results);
+void ddm_data_query_error    (DDMDataQuery *query,
+                              DDMDataError  error,
+                              const char   *message);
+
+/* Debugging convenience */
+const char *ddm_data_query_get_id_string (DDMDataQuery *query);
+
 G_END_DECLS
 
 #endif /* __DDM_DATA_QUERY_H__ */

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-resource-internal.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-resource-internal.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-resource-internal.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -4,15 +4,34 @@
 
 #include <ddm/ddm-data-fetch.h>
 #include <ddm/ddm-data-resource.h>
+#include <ddm/ddm-data-model.h>
+#include "ddm-client-notification.h"
 
 G_BEGIN_DECLS
 
-DDMDataResource *_ddm_data_resource_new (const char *resource_id,
-                                         const char *class_id,
-                                         gboolean    local);
+DDMDataResource *_ddm_data_resource_new (DDMDataModel    *model,
+                                         const char      *resource_id,
+                                         const char      *class_id,
+                                         gboolean         local);
 
 GSList *_ddm_data_resource_get_default_properties (DDMDataResource *resource);
 
+DDMDataFetch *_ddm_data_resource_get_received_fetch   (DDMDataResource *resource);
+DDMDataFetch *_ddm_data_resource_get_requested_fetch  (DDMDataResource *resource);
+gint64        _ddm_data_resource_get_requested_serial (DDMDataResource *resource);
+
+void _ddm_data_resource_fetch_requested (DDMDataResource *resource,
+                                         DDMDataFetch    *fetch,
+                                         guint64          serial);
+void _ddm_data_resource_fetch_received  (DDMDataResource *resource,
+                                         DDMDataFetch    *received_fetch);
+
+void _ddm_data_resource_send_local_notifications (DDMDataResource    *resource,
+                                                  GSList             *changed_properties);
+
+void _ddm_data_resource_resolve_notifications (DDMDataResource          *resource,
+                                               DDMClientNotificationSet *notification_set);
+
 void _ddm_data_resource_dump(DDMDataResource *resource);
 
 G_END_DECLS

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-resource.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-resource.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-resource.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -5,6 +5,7 @@
 #include <stdlib.h>
 
 #include "ddm-data-fetch.h"
+#include "ddm-data-model-internal.h"
 #include "ddm-data-resource-internal.h"
 
 typedef enum {
@@ -15,6 +16,7 @@
 
 typedef struct _DataProperty DataProperty;
 typedef struct _DataConnection DataConnection;
+typedef struct _DataClient DataClient;
 
 struct _DDMDataProperty {
     DDMQName *qname;
@@ -35,14 +37,27 @@
     gpointer user_data;
 };
 
+struct _DataClient {
+    DDMClient *client;
+    DDMDataFetch *fetch;
+};
+
 struct _DDMDataResource
 {
+    DDMDataModel *model;
     char *resource_id;
     char *class_id;
     gboolean local;
 
-    GSList *connections;
+    GSList *clients;
+    GSList *connections; /* Local connections */
     GSList *properties;
+    GSList *changed_properties;
+
+    DDMDataFetch *received_fetch;
+    DDMDataFetch *requested_fetch;
+    gint64 requested_serial;
+
 };
 
 GQuark
@@ -127,7 +142,7 @@
                             DDMDataValue    *value)
 {
     g_return_if_fail(property != NULL);
-    
+
     *value = property->value;
 }
 
@@ -164,15 +179,21 @@
 }
 
 DDMDataResource *
-_ddm_data_resource_new(const char *resource_id,
-                       const char *class_id,
-                       gboolean    local)
+_ddm_data_resource_new(DDMDataModel *model,
+                       const char   *resource_id,
+                       const char   *class_id,
+                       gboolean      local)
 {
     DDMDataResource *resource = g_new0(DDMDataResource, 1);
 
+    resource->model = model;
     resource->resource_id = g_strdup(resource_id);
     ddm_data_resource_set_class_id(resource, class_id);
     resource->local = local != FALSE;
+    resource->requested_fetch = NULL;
+    resource->requested_serial = -1;
+    resource->received_fetch = NULL;
+    resource->local = local;
 
     return resource;
 }
@@ -206,7 +227,7 @@
 }
 
 gboolean
-ddm_data_resource_get_local (DDMDataResource *resource)
+ddm_data_resource_is_local (DDMDataResource *resource)
 {
     g_return_val_if_fail(resource != NULL, FALSE);
 
@@ -441,8 +462,7 @@
         DDMDataProperty *property = l->data;
         
         if (qname == property->qname)
-            return property;
-    }
+            return property;    }
     
     return NULL;
 }
@@ -469,10 +489,10 @@
 }
 
 void
-ddm_data_resource_connect(DDMDataResource *resource,
-                          const char        *property,
-                          DDMDataFunction  function,
-                          gpointer           user_data)
+ddm_data_resource_connect (DDMDataResource *resource,
+                           const char      *property,
+                           DDMDataFunction  function,
+                           gpointer         user_data)
 {
     DataConnection *connection;
 
@@ -494,7 +514,7 @@
 ddm_data_resource_connect_by_qname (DDMDataResource *resource,
                                     DDMQName        *property,
                                     DDMDataFunction  function,
-                                    gpointer           user_data)
+                                    gpointer         user_data)
 {
     DataConnection *connection;
 
@@ -513,6 +533,37 @@
 }
 
 void
+ddm_data_resource_set_client_fetch (DDMDataResource *resource,
+                                    DDMClient       *client,
+                                    DDMDataFetch    *fetch)
+{
+    GSList *l;
+    DataClient *data_client;
+
+    for (l = resource->clients; l; l = l->next) {
+        data_client = l->data;
+        if (data_client->client == client) {
+            if (fetch)
+                ddm_data_fetch_ref(fetch);
+            
+            ddm_data_fetch_unref(data_client->fetch);
+            if (fetch) {
+                data_client->fetch = fetch;
+            } else {
+                resource->clients = g_slist_remove(resource->clients, data_client);
+                g_free(data_client);
+            }
+
+            return;
+        }
+    }
+
+    data_client = g_new(DataClient, 1);
+    data_client->client = client;
+    data_client->fetch = ddm_data_fetch_ref(fetch);
+}
+
+void
 ddm_data_resource_disconnect (DDMDataResource *resource,
                               DDMDataFunction  function,
                               gpointer           user_data)
@@ -913,12 +964,17 @@
             property->default_children = ddm_data_fetch_from_string(default_children);
     }
 
+    if (changed && g_slist_find(resource->changed_properties, property->qname) == NULL) {
+        resource->changed_properties = g_slist_prepend(resource->changed_properties, property->qname);
+        _ddm_data_model_mark_changed(resource->model, resource);
+    }
+
     return changed;
 }
 
 void
-ddm_data_resource_on_resource_change(DDMDataResource *resource,
-                                     GSList            *changed_properties)
+_ddm_data_resource_send_local_notifications (DDMDataResource *resource,
+                                             GSList          *changed_properties)
 {
     GSList *connection_node = resource->connections;
     GSList *property_node;
@@ -955,6 +1011,47 @@
     }
 }
 
+static gboolean
+data_resource_needs_local_notifications (DDMDataResource *resource,
+                                         GSList          *changed_properties)
+{
+    GSList *connection_node = resource->connections;
+    GSList *property_node;
+
+    while (connection_node) {
+        GSList *next = connection_node->next;
+        DataConnection *connection = connection_node->data;
+        
+        if (connection->type == CONNECTION_TYPE_ANY)
+            return TRUE;
+
+        connection_node = next;
+    }
+
+    for (property_node = changed_properties; property_node != NULL; property_node = property_node->next) {
+        DDMQName *property_id = property_node->data;
+            
+        connection_node = resource->connections;
+        
+        while (connection_node) {
+            GSList *next = connection_node->next;
+            DataConnection *connection = connection_node->data;
+            
+            if (connection->type == CONNECTION_TYPE_NAME) {
+                if (connection->match.name == property_id->name)
+                    return TRUE;
+            } else if (connection->type == CONNECTION_TYPE_QNAME) {
+                if (connection->match.qname == property_id)
+                    return TRUE;
+            }
+            
+            connection_node = next;
+        }
+    }
+
+    return FALSE;
+}
+
 gboolean
 ddm_data_parse_type(const char           *type_string,
                     DDMDataType          *type,
@@ -1218,3 +1315,84 @@
     }
 }
 
+DDMDataFetch *
+_ddm_data_resource_get_received_fetch (DDMDataResource *resource)
+{
+    return resource->received_fetch;
+}
+
+DDMDataFetch *
+_ddm_data_resource_get_requested_fetch (DDMDataResource *resource)
+{
+    return resource->requested_fetch;
+}
+
+gint64
+_ddm_data_resource_get_requested_serial (DDMDataResource *resource)
+{
+    return resource->requested_serial;
+}
+
+void
+_ddm_data_resource_fetch_requested (DDMDataResource *resource,
+                                    DDMDataFetch    *fetch,
+                                    guint64          serial)
+{
+    if (resource->requested_fetch == NULL) {
+        resource->requested_fetch = ddm_data_fetch_ref(fetch);
+    } else {
+        DDMDataFetch *old_requested = resource->requested_fetch;
+        resource->requested_fetch = ddm_data_fetch_merge(old_requested, fetch);
+        ddm_data_fetch_unref(old_requested);
+    }
+
+    resource->requested_serial = serial;
+}
+
+void
+_ddm_data_resource_fetch_received (DDMDataResource *resource,
+                                   DDMDataFetch    *received_fetch)
+{
+    if (resource->received_fetch == NULL) {
+        resource->received_fetch = ddm_data_fetch_ref(received_fetch);
+    } else {
+        DDMDataFetch *old_received = resource->received_fetch;
+        resource->received_fetch = ddm_data_fetch_merge(old_received, received_fetch);
+        ddm_data_fetch_unref(old_received);
+    }
+}
+
+void
+_ddm_data_resource_resolve_notifications (DDMDataResource          *resource,
+                                          DDMClientNotificationSet *notification_set)
+{
+    GSList *l;
+    
+    for (l = resource->clients; l; l = l->next) {
+        DataClient *data_client = l->data;
+
+        _ddm_client_notification_set_add(notification_set,
+                                         resource,
+                                         data_client->client,
+                                         data_client->fetch,
+                                         resource->changed_properties);
+    }
+
+    if (data_resource_needs_local_notifications(resource, resource->changed_properties)) {
+        /* received_fetch here is an overestimate, since it covers both local connections
+         * and connections on behalf of clients; we possibly should keep separetely
+         * the fetches resulting from locally-generated queries. And overestimate doesn't
+         * hurt much, however.
+         *
+         * See also comment in ddm-data-query.c:mark_received_fetches()
+         */
+        _ddm_client_notification_set_add(notification_set,
+                                         resource,
+                                         _ddm_data_model_get_local_client(resource->model),
+                                         resource->received_fetch,
+                                         resource->changed_properties);
+    }
+
+    g_slist_free(resource->changed_properties);
+    resource->changed_properties = NULL;
+}

Modified: dumbhippo/trunk/client/common/ddm/ddm-data-resource.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-data-resource.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-data-resource.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -65,6 +65,9 @@
 typedef struct _DDMDataFetch         DDMDataFetch; /* Avoid circular include */
 typedef struct _DDMDataResource      DDMDataResource;
 
+typedef struct _DDMClient            DDMClient;    /* Avoid circular include */
+
+
 typedef void (*DDMDataFunction) (DDMDataResource *resource,
                                  GSList            *changed_properties,
                                  gpointer           user_data);
@@ -89,21 +92,25 @@
 
 const char *ddm_data_resource_get_resource_id (DDMDataResource *resource);
 const char *ddm_data_resource_get_class_id    (DDMDataResource *resource);
-gboolean    ddm_data_resource_get_local       (DDMDataResource *resource);
+gboolean    ddm_data_resource_is_local        (DDMDataResource *resource);
 
 void ddm_data_resource_get               (DDMDataResource *resource,
                                           ...) G_GNUC_NULL_TERMINATED;
 void ddm_data_resource_get_by_qname      (DDMDataResource *resource,
                                           ...) G_GNUC_NULL_TERMINATED;
 
-void ddm_data_resource_connect          (DDMDataResource   *resource,
-                                         const char        *property,
-                                         DDMDataFunction    function,
-                                         gpointer           user_data);
+void ddm_data_resource_connect          (DDMDataResource *resource,
+                                         const char      *property,
+                                         DDMDataFunction  function,
+                                         gpointer         user_data);
 void ddm_data_resource_connect_by_qname (DDMDataResource *resource,
                                          DDMQName        *property,
                                          DDMDataFunction  function,
                                          gpointer         user_data);
+void ddm_data_resource_set_client_fetch (DDMDataResource *resource,
+                                         DDMClient       *client,
+                                         DDMDataFetch    *fetch);
+
 void ddm_data_resource_disconnect       (DDMDataResource *resource,
                                          DDMDataFunction  function,
                                          gpointer         user_data);
@@ -127,8 +134,6 @@
 DDMDataProperty *  ddm_data_resource_get_property_by_qname (DDMDataResource    *resource,
                                                             DDMQName           *qname);
 GSList          *  ddm_data_resource_get_properties        (DDMDataResource    *resource);
-void               ddm_data_resource_on_resource_change    (DDMDataResource    *resource,
-                                                            GSList             *changed_properties);
 
 gboolean ddm_data_value_from_string (DDMDataValue *value,
                                      DDMDataType   type,

Added: dumbhippo/trunk/client/common/ddm/ddm-local-client.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-local-client.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-local-client.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,86 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include "ddm-data-resource-internal.h"
+#include "ddm-local-client.h"
+
+static void ddm_local_client_iface_init(DDMClientIface *iface);
+
+struct _DDMLocalClient {
+    GObject parent;
+
+    DDMDataModel *model;
+};
+
+struct _DDMLocalClientClass {
+    GObjectClass parent_class;
+};
+
+#define ddm_local_client_get_type _ddm_local_client_get_type
+
+G_DEFINE_TYPE_WITH_CODE(DDMLocalClient, ddm_local_client, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE(DDM_TYPE_CLIENT, ddm_local_client_iface_init);)
+
+#undef ddm_local_client_get_type
+
+static void
+ddm_local_client_dispose(GObject *object)
+{
+}
+
+static void
+ddm_local_client_finalize(GObject *object)
+{
+}
+
+static void
+ddm_local_client_init(DDMLocalClient *local_client)
+{
+}
+
+static void
+ddm_local_client_class_init(DDMLocalClientClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->dispose = ddm_local_client_dispose;
+    object_class->finalize = ddm_local_client_finalize;
+}
+
+static gpointer
+ddm_local_client_begin_notification (DDMClient *client)
+{
+    return NULL;
+}
+
+static void
+ddm_local_client_notify (DDMClient       *client,
+                         DDMDataResource *resource,
+                         GSList          *changed_properties,
+                         gpointer         notification_data)
+{
+    _ddm_data_resource_send_local_notifications(resource, changed_properties);
+}
+
+static void
+ddm_local_client_end_notification (DDMClient       *client,
+                                   gpointer         notification_data)
+{
+}
+
+static void
+ddm_local_client_iface_init(DDMClientIface *iface)
+{
+    iface->begin_notification = ddm_local_client_begin_notification;
+    iface->notify = ddm_local_client_notify;
+    iface->end_notification = ddm_local_client_end_notification;
+}
+
+DDMClient *
+_ddm_local_client_new (DDMDataModel *model)
+{
+    DDMLocalClient *local_client = g_object_new(DDM_TYPE_LOCAL_CLIENT, NULL);
+
+    local_client->model = model;
+
+    return DDM_CLIENT(local_client);
+}

Added: dumbhippo/trunk/client/common/ddm/ddm-local-client.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-local-client.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-local-client.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#ifndef __DDM_LOCAL_CLIENT_H__
+#define __DDM_LOCAL_CLIENT_H__
+
+#include "ddm-client.h"
+#include "ddm-data-model.h"
+
+G_BEGIN_DECLS
+
+/* Stub client used for connections directly to the data model made within
+ * process; most of the real handling of this is inside DDMDataResource -
+ * for example, we track the connections directly on the DDMDataResource.
+ */
+
+typedef struct _DDMLocalClient      DDMLocalClient;
+typedef struct _DDMLocalClientClass DDMLocalClientClass;
+
+#define DDM_TYPE_LOCAL_CLIENT              (_ddm_local_client_get_type ())
+#define DDM_LOCAL_CLIENT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), DDM_TYPE_LOCAL_CLIENT, DDMLocalClient))
+#define DDM_LOCAL_CLIENT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), DDM_TYPE_LOCAL_CLIENT, DDMLocalClientClass))
+#define DDM_IS_LOCAL_CLIENT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), DDM_TYPE_LOCAL_CLIENT))
+#define DDM_IS_LOCAL_CLIENT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), DDM_TYPE_LOCAL_CLIENT))
+#define DDM_LOCAL_CLIENT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), DDM_TYPE_LOCAL_CLIENT, DDMLocalClientClass))
+
+GType _ddm_local_client_get_type (void) G_GNUC_CONST;
+
+DDMClient *_ddm_local_client_new (DDMDataModel *model);
+
+G_END_DECLS
+
+#endif /* __DDM_LOCAL_CLIENT_H__ */

Modified: dumbhippo/trunk/client/common/ddm/ddm-notification-set.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-notification-set.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-notification-set.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -71,10 +71,12 @@
                           gpointer value,
                           gpointer data)
 {
+#if 0    
     /* DDMNotificationSet *notifications = data; */
     ResourceInfo *info = value;
 
     ddm_data_resource_on_resource_change(info->resource, info->changed_properties);
+#endif
 }
 
 void

Added: dumbhippo/trunk/client/common/ddm/ddm-work-item.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-work-item.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-work-item.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,365 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include "ddm-work-item.h"
+#include "ddm-data-model-internal.h"
+#include "ddm-data-query-internal.h"
+#include "ddm-data-resource-internal.h"
+
+typedef struct {
+    DDMDataResource *resource;
+    DDMDataFetch *fetch;
+    GSList *changed_properties;
+} WorkItemNotifyResource;
+
+struct _DDMWorkItem {
+    guint refcount;
+
+    enum {
+        ITEM_NOTIFY,
+        ITEM_QUERY_RESPONSE
+    } type;
+
+    DDMDataModel *model;
+    DDMDataFetch *fetch;
+    gint64 min_serial;
+
+    union {
+        struct {
+            DDMClient *client;
+            GHashTable *resources;
+        } notify;
+        
+        struct {
+            DDMDataQuery *query;
+        } query_response;
+    } u;
+
+    char *id_string;
+};
+
+static WorkItemNotifyResource *
+work_item_notify_resource_new (DDMDataResource *resource,
+                               DDMDataFetch    *fetch,
+                               GSList          *changed_properties)
+{
+    WorkItemNotifyResource *notify_resource = g_new0(WorkItemNotifyResource, 1);
+    
+    notify_resource->resource = resource;
+    notify_resource->fetch = ddm_data_fetch_ref(fetch);
+    notify_resource->changed_properties = g_slist_copy(changed_properties);
+
+    return notify_resource;
+}
+
+static void
+work_item_notify_resource_free (WorkItemNotifyResource *notify_resource)
+{
+    ddm_data_fetch_unref(notify_resource->fetch);
+    g_slist_free(notify_resource->changed_properties);
+    
+    g_free(notify_resource);
+}
+
+DDMWorkItem *
+_ddm_work_item_notify_client_new (DDMDataModel *model,
+                                  DDMClient    *client)
+{
+    DDMWorkItem *item = g_new0(DDMWorkItem, 1);
+    item->refcount = 1;
+    item->model = model;
+    item->type = ITEM_NOTIFY;
+    item->min_serial = -1;
+
+    item->u.notify.client = g_object_ref(client);
+    item->u.notify.resources = g_hash_table_new_full(g_direct_hash, NULL,
+                                                     NULL,
+                                                     (GDestroyNotify)work_item_notify_resource_free);
+    if (client == _ddm_data_model_get_local_client(model))
+        item->id_string = g_strdup_printf("Notify-Local");
+    else
+        item->id_string = g_strdup_printf("Notify-%p", client);
+
+    return item;
+}
+
+void
+_ddm_work_item_notify_client_add (DDMWorkItem     *item,
+                                  DDMDataResource *resource,
+                                  DDMDataFetch    *fetch,
+                                  GSList          *changed_properties)
+{
+    WorkItemNotifyResource *notify_resource;
+    
+    g_return_if_fail (item->type == ITEM_NOTIFY);
+
+    notify_resource = g_hash_table_lookup(item->u.notify.resources, resource);
+    if (notify_resource != NULL) {
+        /* Not sure that this justifies a hash table, but I have some idea that we might
+         * need to track more per-resource state in a way that changes as we resolve
+         * fetches.
+         */
+        g_warning("Duplicate call to _ddm_work_item_notify_client_add for the same resource");
+        return;
+    }
+    
+    notify_resource = work_item_notify_resource_new(resource, fetch, changed_properties);
+    g_hash_table_insert(item->u.notify.resources, resource, notify_resource);
+}
+
+DDMWorkItem *
+_ddm_work_item_query_response_new (DDMDataModel *model,
+				   DDMDataQuery *query)
+{
+    DDMWorkItem *item = g_new0(DDMWorkItem, 1);
+    item->refcount = 1;
+    item->model = model;
+    item->type = ITEM_QUERY_RESPONSE;
+    item->min_serial = -1;
+
+    item->u.query_response.query = query;
+
+    item->id_string = g_strdup_printf("QueryResponse-%" G_GINT64_MODIFIER "d", _ddm_data_query_get_serial(query));
+    
+    return item;
+}
+
+DDMWorkItem *
+_ddm_work_item_ref (DDMWorkItem *item)
+{
+    g_return_val_if_fail(item != NULL, NULL);
+    g_return_val_if_fail(item->refcount > 0, NULL);
+
+    item->refcount++;
+
+    return item;
+}
+
+void
+_ddm_work_item_unref (DDMWorkItem *item)
+{
+    g_return_if_fail(item != NULL);
+    g_return_if_fail(item->refcount > 0);
+
+    item->refcount--;
+    if (item->refcount == 0) {
+        switch (item->type) {
+        case ITEM_NOTIFY:
+            g_object_unref(item->u.notify.client);
+            g_hash_table_destroy(item->u.notify.resources);
+            break;
+        case ITEM_QUERY_RESPONSE:
+            break;
+        }
+
+        g_free(item);
+    }
+}
+
+static void
+item_fetch_additional_at_resource(DDMWorkItem     *item,
+                                  DDMDataResource *resource,
+                                  DDMDataFetch    *unrequested_fetch)
+{
+    const char *resource_id = ddm_data_resource_get_resource_id(resource);
+    char *fetch_string = ddm_data_fetch_to_string(unrequested_fetch);
+    DDMDataQuery *query;
+    gint64 serial;
+
+    g_debug("%s: Must make additional request for %s, fetch=%s",
+            item->id_string, resource_id, fetch_string);
+
+    query = _ddm_data_model_query_remote_resource(item->model, resource_id, fetch_string);
+    serial = _ddm_data_query_get_serial(query);
+
+    _ddm_data_resource_fetch_requested(resource, unrequested_fetch, serial);
+    item->min_serial = MAX(item->min_serial, serial);
+    
+    g_free(fetch_string);
+}
+
+static gboolean
+item_fetch_additional(DDMWorkItem     *item,
+                      DDMDataResource *resource,
+                      DDMDataFetch    *fetch)
+{
+    DDMDataFetchIter iter;
+    gboolean all_satisfied = TRUE;
+
+    if (fetch == NULL)
+        return TRUE;
+
+    if (!ddm_data_resource_is_local(resource)) {
+        DDMDataFetch *received_fetch;
+        DDMDataFetch *unreceived_fetch;
+        
+        received_fetch = _ddm_data_resource_get_received_fetch(resource);
+        if (received_fetch != NULL) {
+            unreceived_fetch = ddm_data_fetch_subtract(fetch, received_fetch);
+        } else {
+            unreceived_fetch = ddm_data_fetch_ref(fetch);
+        }
+
+        if (unreceived_fetch != NULL) {
+            DDMDataFetch *requested_fetch;
+            DDMDataFetch *unrequested_fetch;
+            
+            requested_fetch = _ddm_data_resource_get_requested_fetch(resource);
+            if (requested_fetch != NULL) {
+                unrequested_fetch = ddm_data_fetch_subtract(unreceived_fetch, requested_fetch);
+            } else {
+                unrequested_fetch = ddm_data_fetch_ref(unreceived_fetch);
+            }
+
+            if (unrequested_fetch != NULL) {
+                item_fetch_additional_at_resource(item, resource, unrequested_fetch);
+            } else {
+                gint64 old_serial = _ddm_data_resource_get_requested_serial(resource);
+                item->min_serial = MAX(item->min_serial, old_serial);
+            }
+
+            all_satisfied = FALSE;
+            
+            if (unrequested_fetch != NULL)
+                ddm_data_fetch_unref(unrequested_fetch);
+        }
+
+        if (unreceived_fetch != NULL)
+            ddm_data_fetch_unref(unreceived_fetch);
+    }
+
+    ddm_data_fetch_iter_init(&iter, resource, fetch);
+
+    while (ddm_data_fetch_iter_has_next(&iter)) {
+        DDMDataProperty *property;
+        DDMDataFetch *children;
+        DDMDataValue value;
+        
+        ddm_data_fetch_iter_next(&iter, &property, &children);
+
+        if (children != NULL) {
+            ddm_data_property_get_value(property, &value);
+            
+            g_assert (DDM_DATA_BASE(value.type) == DDM_DATA_RESOURCE);
+            
+            if (DDM_DATA_IS_LIST(value.type)) {
+                GSList *l;
+                
+                for (l = value.u.list; l; l = l->next) {
+                    if (!item_fetch_additional(item, l->data, children))
+                        all_satisfied = FALSE;
+                }
+            } else {
+                if (!item_fetch_additional(item, value.u.resource, children))
+                    all_satisfied = FALSE;
+            }
+        }
+    }
+    
+    ddm_data_fetch_iter_clear(&iter);
+
+    return all_satisfied;
+}
+
+typedef struct {
+    DDMWorkItem *item;
+    gboolean all_satisfied;
+} NotifyAddAdditionalClosure;
+
+static void
+notify_add_additional_foreach(gpointer key,
+                              gpointer value,
+                              gpointer data)
+{
+    WorkItemNotifyResource *notify_resource = value;
+    NotifyAddAdditionalClosure *closure = data;
+
+    if (!item_fetch_additional(closure->item, notify_resource->resource, notify_resource->fetch))
+        closure->all_satisfied = FALSE;
+}
+
+typedef struct {
+    DDMWorkItem *item;
+    gpointer notification_data;
+} NotifyProcessClosure;
+
+static void
+notify_process_foreach(gpointer key,
+                       gpointer value,
+                       gpointer data)
+{
+    WorkItemNotifyResource *notify_resource = value;
+    NotifyProcessClosure *closure = data;
+
+    ddm_client_notify(closure->item->u.notify.client,
+                      notify_resource->resource,
+                      notify_resource->changed_properties,
+                      closure->notification_data);
+}
+
+gboolean
+_ddm_work_item_process (DDMWorkItem *item)
+{
+    GSList *l;
+    gboolean all_satisfied = TRUE;
+
+    switch (item->type) {
+    case ITEM_NOTIFY:
+        {
+            NotifyAddAdditionalClosure closure;
+
+            closure.item = item;
+            closure.all_satisfied = all_satisfied;
+            
+            g_hash_table_foreach(item->u.notify.resources, notify_add_additional_foreach, &closure);
+
+            all_satisfied = closure.all_satisfied;
+        }
+        break;
+    case ITEM_QUERY_RESPONSE:
+        {
+            GSList *resources = ddm_data_query_get_results(item->u.query_response.query);
+            for (l = resources; l; l = l->next) {
+                DDMDataResource *resource = l->data;
+                
+                if (!item_fetch_additional(item, resource,
+                                           ddm_data_query_get_fetch(item->u.query_response.query)))
+                    all_satisfied = FALSE;
+            }
+        }
+        break;
+    }
+
+    if (all_satisfied) {
+        g_debug("%s: all fetches are satisfied", item->id_string);
+        
+        switch (item->type) {
+        case ITEM_NOTIFY:
+            {
+                NotifyProcessClosure closure;
+                
+                closure.item = item;
+                closure.notification_data = ddm_client_begin_notification(item->u.notify.client);
+
+                g_hash_table_foreach(item->u.notify.resources, notify_process_foreach, &closure);
+
+                ddm_client_end_notification(item->u.notify.client,
+                                            closure.notification_data);
+            }
+            break;
+        case ITEM_QUERY_RESPONSE:
+            _ddm_data_query_run_response(item->u.query_response.query);
+            break;
+        }
+    } else {
+        g_debug("%s: have unsatisfied fetches; need responses; min_serial=%" G_GINT64_MODIFIER "d", item->id_string,
+                item->min_serial);
+    }
+
+    return all_satisfied;
+}
+
+gint64
+_ddm_work_item_get_min_serial (const DDMWorkItem *item)
+{
+    return item->min_serial;
+}

Added: dumbhippo/trunk/client/common/ddm/ddm-work-item.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/ddm-work-item.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/ddm-work-item.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#ifndef __DDM_WORK_ITEM_H__
+#define __DDM_WORK_ITEM_H__
+
+#include "ddm-client.h"
+#include "ddm-data-fetch.h"
+#include "ddm-data-model.h"
+
+G_BEGIN_DECLS
+
+/* A "work item", is a task that is queued pending on completion of all
+ * fetches needed to execute it.
+ *
+ * We have two types of work item currently:
+ *
+ *  - An pending notification, either externally to a "client", or locally.
+ *    (represented as being to to the "local client")
+ *  - The response to a query IQ
+ */
+
+typedef struct _DDMWorkItem DDMWorkItem;
+
+DDMWorkItem *_ddm_work_item_notify_client_new (DDMDataModel    *model,
+					       DDMClient       *client);
+
+void _ddm_work_item_notify_client_add (DDMWorkItem     *item,
+				       DDMDataResource *resource,
+				       DDMDataFetch    *fetch,
+				       GSList          *changed_properties);
+
+DDMWorkItem *_ddm_work_item_query_response_new (DDMDataModel    *model,
+						DDMDataQuery    *query);
+
+DDMWorkItem *_ddm_work_item_ref              (DDMWorkItem *item);
+void         _ddm_work_item_unref            (DDMWorkItem *item);
+
+
+/* Try to execute the item; a TRUE return means that it was executed,
+ * and can be freed; a FALSE return means that additional fetches
+ * have been sent upstream, item->min_serial has been updated, and
+ * the item needs to be requeued.
+ */
+gboolean _ddm_work_item_process (DDMWorkItem *item);
+
+/* The item can't continue until a response has been received for
+ * this query serial */
+gint64       _ddm_work_item_get_min_serial   (const DDMWorkItem *item);
+
+G_END_DECLS
+
+#endif /* __DDM_WORK_ITEM_H__ */

Modified: dumbhippo/trunk/client/common/ddm/static-file-backend.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/static-file-backend.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/static-file-backend.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -22,7 +22,6 @@
 
 typedef struct {
     DDMDataModel *ddm_model;
-    int idle_source;
     GSList *pending_requests;
 
     DDMDataModel *backend_model;
@@ -255,9 +254,6 @@
     
     g_object_set_data(G_OBJECT(ddm_model), "dbus-data-model", NULL);
 
-    if (static_file_model->idle_source)
-        g_source_remove(static_file_model->idle_source);
-
     while (static_file_model->pending_requests != NULL) {
         PendingRequest *pr = static_file_model->pending_requests->data;
         static_file_model->pending_requests = g_slist_delete_link(static_file_model->pending_requests,
@@ -274,25 +270,12 @@
     g_free(static_file_model);
 }
 
-static gboolean
-pending_request_idle(gpointer data)
-{
-    StaticFileModel *static_file_model = data;
-
-    ddm_static_file_model_flush(static_file_model->ddm_model);
-    
-    return FALSE;
-}
-
 static void
 model_add_pending_request(StaticFileModel *static_file_model,
                           PendingRequest  *pr)
 {
     static_file_model->pending_requests = g_slist_append(static_file_model->pending_requests, pr);
-
-    if (static_file_model->idle_source == 0) {
-        static_file_model->idle_source = g_idle_add(pending_request_idle, static_file_model);
-    }
+    ddm_data_model_schedule_flush(static_file_model->ddm_model);
 }
 
 static void
@@ -329,12 +312,35 @@
     model_add_pending_request(static_file_model, pr);
 }
 
+static void
+static_file_flush (DDMDataModel *model,
+                   void         *backend_data)
+{
+    StaticFileModel *static_file_model = get_static_file_model(model);
+
+    while (static_file_model->pending_requests != NULL) {
+        PendingRequest *pr = static_file_model->pending_requests->data;
+        static_file_model->pending_requests = g_slist_remove(static_file_model->pending_requests,
+                                                             static_file_model->pending_requests->data);
+        
+        if (pr->type == PENDING_REQUEST_QUERY) {
+            model_process_query(static_file_model, pr->query);
+        } else if (pr->type == PENDING_REQUEST_UPDATE) {
+            model_process_update(static_file_model, pr->query);
+        } else {
+            g_error("unknown pending request type");
+        }
+        
+        g_free(pr);
+    }
+}
+
 static const DDMDataModelBackend static_file_backend = {
     static_file_add_model,
     static_file_remove_model,
     static_file_send_query,
     static_file_send_update,
-    NULL,
+    static_file_flush
 };
 
 DDMDataModel*
@@ -356,29 +362,3 @@
     return model;
 }
 
-void
-ddm_static_file_model_flush (DDMDataModel *model)
-{
-    StaticFileModel *static_file_model = get_static_file_model(model);
-
-    while (static_file_model->pending_requests != NULL) {
-        PendingRequest *pr = static_file_model->pending_requests->data;
-        static_file_model->pending_requests = g_slist_remove(static_file_model->pending_requests,
-                                                             static_file_model->pending_requests->data);
-        
-        if (pr->type == PENDING_REQUEST_QUERY) {
-            model_process_query(static_file_model, pr->query);
-        } else if (pr->type == PENDING_REQUEST_UPDATE) {
-            model_process_update(static_file_model, pr->query);
-        } else {
-            g_error("unknown pending request type");
-        }
-        
-        g_free(pr);
-    }
-
-    if (static_file_model->idle_source != 0) {
-        g_source_remove(static_file_model->idle_source);
-        static_file_model->idle_source = 0;
-    }
-}

Modified: dumbhippo/trunk/client/common/ddm/static-file-backend.h
===================================================================
--- dumbhippo/trunk/client/common/ddm/static-file-backend.h	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/static-file-backend.h	2007-11-01 23:17:41 UTC (rev 6864)
@@ -8,23 +8,22 @@
 DDMDataModel *ddm_static_file_model_new (const char    *filename,
 					 GError       **error);
 
-/* Processes all pending requests, avoids having to run the main loop
- */
-void ddm_static_file_model_flush (DDMDataModel *model);
-
 /* Used internally to ddm_static_file_model_new
  */
 gboolean ddm_static_file_parse (const char    *filename,
 				DDMDataModel  *model,
 				GError       **error);
 
-/* This one one uses the same XML parsing mechanism that is used to
+/* These use the same XML parsing mechanism that is used to
  * load the backend model to fill in local resources in the frontend
  * model (or any other model.)
  */
-gboolean ddm_static_load_local_string(const char   *str,
-				      DDMDataModel *mode,
-				      GError      **error);
+gboolean ddm_static_load_local_file  (const char    *filename,
+				      DDMDataModel  *model,
+				      GError       **error);
+gboolean ddm_static_load_local_string (const char    *str,
+				       DDMDataModel  *mode,
+				       GError       **error);
 
 G_END_DECLS
 

Modified: dumbhippo/trunk/client/common/ddm/static-file-parser.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/static-file-parser.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/static-file-parser.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -128,6 +128,7 @@
     
     info->model = model;
     info->state = STATE_OUTSIDE;
+    info->local = local;
 
     info->names[NAME_INVALID] = NULL;
     info->names[NAME_DEFAULT_CHILDREN] = ddm_qname_get(SYSTEM_NAMESPACE, "defaultChildren");
@@ -625,16 +626,31 @@
             if (info->current_update == DDM_DATA_UPDATE_ADD) {
                 if (info->current_type == DDM_DATA_RESOURCE) {
                     value.type = DDM_DATA_RESOURCE;
+
+                    /* We create the resource, even if we don't know it's class_id; this is useful
+                     * at least for the local data case where we want to point to some remote
+                     * resource that hasn't been fetched yet. The class_id will be filled in later
+                     * once we fetch. We have to do the lookup/ensure in two stages, since we don't
+                     * know that it's a remote resource until we don't find it from the local parsing.
+                     */
+#if 1
                     value.u.resource = ddm_data_model_lookup_resource(info->model, info->value->str);
+                    if (value.u.resource == NULL)
+                        value.u.resource = ddm_data_model_ensure_resource(info->model, info->value->str, NULL);
+#else
+                    /* The alternative */
+                    value.u.resource = ddm_data_model_lookup_resource(info->model, info->value->str);
                     
                     if (value.u.resource == NULL) {
                         g_set_error (error,
                                      G_MARKUP_ERROR,
                                      G_MARKUP_ERROR_INVALID_CONTENT,
-                                 "Reference to a resource %s that we don't know about",
+                                     "Reference to a resource %s that we don't know about",
+                                     
                                      info->value->str);
-                    return;
+                        return;
                     }
+#endif                        
                 } else {
                     if (!ddm_data_value_from_string(&value, info->current_type, info->value->str,
                                                     NULL, error))
@@ -735,24 +751,21 @@
 {
 }
 
-gboolean
-ddm_static_file_parse(const char   *filename,
-                      DDMDataModel *model,
-                      GError      **error)
+static gboolean
+static_file_parse_internal(const char   *filename,
+                           DDMDataModel *model,
+                           gboolean      local,
+                           GError      **error)
 {
     SFParseInfo info;
     gboolean result;
     char *text;
     gsize len;
     
-    g_return_val_if_fail(filename != NULL, FALSE);
-    g_return_val_if_fail(DDM_IS_DATA_MODEL(model), FALSE);
-    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
-
     if (!g_file_get_contents(filename, &text, &len, error))
         return FALSE;
     
-    sf_parse_info_init(&info, model, FALSE);
+    sf_parse_info_init(&info, model, local);
     
     result = g_markup_parse_context_parse(info.context, text, len, error);
 
@@ -762,6 +775,30 @@
 }
 
 gboolean
+ddm_static_file_parse(const char   *filename,
+                      DDMDataModel *model,
+                      GError      **error)
+{
+    g_return_val_if_fail(filename != NULL, FALSE);
+    g_return_val_if_fail(DDM_IS_DATA_MODEL(model), FALSE);
+    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+    return static_file_parse_internal(filename, model, FALSE, error);
+}
+
+gboolean
+ddm_static_load_local_file(const char   *filename,
+                           DDMDataModel *model,
+                           GError      **error)
+{
+    g_return_val_if_fail(filename != NULL, FALSE);
+    g_return_val_if_fail(DDM_IS_DATA_MODEL(model), FALSE);
+    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+    return static_file_parse_internal(filename, model, TRUE, error);
+}
+
+gboolean
 ddm_static_load_local_string(const char   *str,
                              DDMDataModel *model,
                              GError      **error)

Added: dumbhippo/trunk/client/common/ddm/test-local-data.xml
===================================================================
--- dumbhippo/trunk/client/common/ddm/test-local-data.xml	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/test-local-data.xml	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?><!-- -*- sgml-indent-step: 4; fill-column: 120 -*- -->
+<m:model xmlns:m="http://mugshot.org/p/system"; m:resourceBase="online-desktop:/o/">
+    <resource xmlns="online-desktop://p/o/buddy" m:resourceId="pidgin-buddy/AIM.JohnDoe1">
+	<user m:type="r?" m:resourceId="http://mugshot.org/o/user/USER1"/>
+    </resource>
+</m:model>

Added: dumbhippo/trunk/client/common/ddm/test-multipart-fetch.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/test-multipart-fetch.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/test-multipart-fetch.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,97 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include <string.h>
+
+#include "static-file-backend.h"
+#include "ddm-data-query.h"
+
+static void
+on_query_result (DDMDataResource *result,
+                 gpointer         user_data)
+{
+    DDMDataResource **result_location = user_data;
+    *result_location = result;
+}
+
+static void
+on_query_error (DDMDataError     error,
+                const char      *message,
+                gpointer         user_data)
+{
+    const char **message_location = user_data;
+    
+    g_assert(message != NULL);
+    *message_location = g_strdup(message);
+}
+
+static DDMDataResource *
+query_resource(DDMDataModel    *model,
+               const char      *resource_id,
+               const char      *fetch)
+{
+    DDMDataQuery *query;
+    DDMDataResource *result = NULL;
+    const char *error = NULL;
+    
+    query = ddm_data_model_query_resource(model, resource_id, fetch);
+    ddm_data_query_set_single_handler(query, on_query_result, &result);
+    ddm_data_query_set_error_handler(query, on_query_error, &error);
+
+    while (ddm_data_model_needs_flush(model))
+        ddm_data_model_flush(model);
+
+    if (error != NULL)
+        g_error("Error from getResource, resource_id=%s, fetch=%s: %s", resource_id, fetch, error);
+
+    return result;
+}
+
+int
+main(int argc, char **argv)
+{
+    GError *error = NULL;
+    DDMDataModel *model;
+    const char *srcdir;
+    char *filename;
+
+    DDMDataResource *result;
+    DDMDataResource *user1;
+    DDMDataResource *buddy1;
+    DDMDataResource *user;
+
+    g_type_init();
+
+    srcdir = g_getenv("DDM_SRCDIR");
+    if (srcdir == NULL)
+        g_error("DDM_SRCDIR is not set");
+
+    filename = g_build_filename(srcdir, "test-data.xml", NULL);
+    model = ddm_static_file_model_new(filename, &error);
+    if (model == NULL)
+        g_error("Failed to create test model: %s", error->message);
+
+    g_free(filename);
+
+    filename = g_build_filename(srcdir, "test-local-data.xml", NULL);
+    if (!ddm_static_load_local_file(filename, model, &error))
+        g_error("Failed to add_local data to test model: %s", error->message);
+
+    g_free(filename);
+
+    result = query_resource(model, "online-desktop:/o/pidgin-buddy/AIM.JohnDoe1", "user name");
+
+    buddy1 = ddm_data_model_lookup_resource(model, "online-desktop:/o/pidgin-buddy/AIM.JohnDoe1");
+    g_assert(buddy1 != NULL);
+
+    user1 = ddm_data_model_lookup_resource(model, "http://mugshot.org/o/user/USER1";);
+    g_assert(user1 != NULL);
+
+    user = NULL;
+    ddm_data_resource_get(buddy1,
+                          "user", DDM_DATA_RESOURCE, &user,
+                          NULL);
+
+    g_assert(user == user1);
+
+    return 0;
+}

Added: dumbhippo/trunk/client/common/ddm/test-notification.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/test-notification.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/test-notification.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -0,0 +1,162 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+
+#include <string.h>
+
+#include "static-file-backend.h"
+#include "ddm-data-query.h"
+
+static DDMDataModel *model = NULL;
+
+static void
+on_query_result (DDMDataResource *result,
+                 gpointer         user_data)
+{
+    DDMDataResource **result_location = user_data;
+    *result_location = result;
+}
+
+static void
+on_query_error (DDMDataError     error,
+                const char      *message,
+                gpointer         user_data)
+{
+    const char **message_location = user_data;
+    
+    g_assert(message != NULL);
+    *message_location = g_strdup(message);
+}
+
+static void
+flush_model(DDMDataModel *model) {
+    while (ddm_data_model_needs_flush(model))
+        ddm_data_model_flush(model);
+
+}
+
+static DDMDataResource *
+query_resource(DDMDataModel    *model,
+               const char      *resource_id,
+               const char      *fetch)
+{
+    DDMDataQuery *query;
+    DDMDataResource *result = NULL;
+    const char *error = NULL;
+    
+    query = ddm_data_model_query_resource(model, resource_id, fetch);
+    ddm_data_query_set_single_handler(query, on_query_result, &result);
+    ddm_data_query_set_error_handler(query, on_query_error, &error);
+
+    flush_model(model);
+
+    if (error != NULL)
+        g_error("Error from getResource, resource_id=%s, fetch=%s: %s", resource_id, fetch, error);
+
+    return result;
+}
+
+static void
+on_buddy1_changed(DDMDataResource *resource,
+                  GSList          *changed_properties,
+                  gpointer         data)
+{
+    gboolean *was_changed = data;
+    DDMDataResource *user;
+    DDMDataResource *user2;
+    const char *name;
+
+    *was_changed = TRUE;
+
+    g_assert(strcmp(ddm_data_resource_get_resource_id(resource), "online-desktop:/o/pidgin-buddy/AIM.JohnDoe1") == 0);
+
+    user2 = ddm_data_model_lookup_resource(model, "http://mugshot.org/o/user/USER2";);
+    g_assert(user2 != NULL);
+
+    user = NULL;
+    ddm_data_resource_get(resource,
+                          "user", DDM_DATA_RESOURCE, &user,
+                          NULL);
+
+    g_assert(user == user2);
+
+    name = NULL;
+    ddm_data_resource_get(user,
+                          "name", DDM_DATA_STRING, &name,
+                          NULL);
+
+    g_assert(strcmp(name, "Sally Smith") == 0);
+}
+
+int
+main(int argc, char **argv)
+{
+    GError *error = NULL;
+    const char *srcdir;
+    char *filename;
+
+    DDMDataResource *result;
+    DDMDataResource *user1;
+    DDMDataResource *user2;
+    DDMDataResource *buddy1;
+    DDMDataResource *user;
+
+    DDMDataValue value;
+
+    gboolean was_changed = FALSE;
+
+    g_type_init();
+
+    srcdir = g_getenv("DDM_SRCDIR");
+    if (srcdir == NULL)
+        g_error("DDM_SRCDIR is not set");
+
+    filename = g_build_filename(srcdir, "test-data.xml", NULL);
+    model = ddm_static_file_model_new(filename, &error);
+    if (model == NULL)
+        g_error("Failed to create test model: %s", error->message);
+
+    g_free(filename);
+
+    filename = g_build_filename(srcdir, "test-local-data.xml", NULL);
+    if (!ddm_static_load_local_file(filename, model, &error))
+        g_error("Failed to add_local data to test model: %s", error->message);
+
+    g_free(filename);
+
+    result = query_resource(model, "online-desktop:/o/pidgin-buddy/AIM.JohnDoe1", "user name");
+
+    buddy1 = ddm_data_model_lookup_resource(model, "online-desktop:/o/pidgin-buddy/AIM.JohnDoe1");
+    g_assert(buddy1 != NULL);
+
+    user1 = ddm_data_model_lookup_resource(model, "http://mugshot.org/o/user/USER1";);
+    g_assert(user1 != NULL);
+
+    user = NULL;
+    ddm_data_resource_get(buddy1,
+                          "user", DDM_DATA_RESOURCE, &user,
+                          NULL);
+
+    g_assert(user == user1);
+
+    ddm_data_resource_connect(buddy1, NULL, on_buddy1_changed, &was_changed);
+
+    /* This change will trigger the immediate change to user1, but it should also
+     * cause a fetch from upstream of the name property of user2, since our stored
+     * fetch for buddy1 is 'user name' and we haven't yet fetched name for user2
+     */
+    user2 = ddm_data_model_ensure_resource(model, "http://mugshot.org/o/user/USER2";, NULL);
+
+    value.type = DDM_DATA_RESOURCE;
+    value.u.resource = user2;
+
+    ddm_data_resource_update_property(buddy1,
+                                      ddm_qname_get("online-desktop://p/o/buddy", "user"),
+                                      DDM_DATA_UPDATE_REPLACE, DDM_DATA_CARDINALITY_01,
+                                      FALSE, NULL,
+                                      &value);
+
+    flush_model(model);
+
+    g_assert(was_changed);
+
+    return 0;
+}

Modified: dumbhippo/trunk/client/common/ddm/test-static-file-backend.c
===================================================================
--- dumbhippo/trunk/client/common/ddm/test-static-file-backend.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/ddm/test-static-file-backend.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -36,9 +36,10 @@
     query = ddm_data_model_query_resource(model, resource_id, fetch);
     ddm_data_query_set_single_handler(query, on_query_result, &result);
     ddm_data_query_set_error_handler(query, on_query_error, &error);
-    
-    ddm_static_file_model_flush(model);
 
+    while (ddm_data_model_needs_flush(model))
+        ddm_data_model_flush(model);
+
     if (error != NULL)
         g_error("Error from getResource, resource_id=%s, fetch=%s: %s", resource_id, fetch, error);
 

Modified: dumbhippo/trunk/client/common/hippo/hippo-data-model-backend.c
===================================================================
--- dumbhippo/trunk/client/common/hippo/hippo-data-model-backend.c	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/common/hippo/hippo-data-model-backend.c	2007-11-01 23:17:41 UTC (rev 6864)
@@ -9,7 +9,6 @@
     DDMDataModel   *ddm_model;
     HippoDataCache *data_cache;
     HippoDiskCache *disk_cache;
-    char           *self_id;
     DDMDataQuery   *self_query;
 } HippoModel;
 
@@ -95,41 +94,16 @@
         value.type = DDM_DATA_RESOURCE;
         value.u.resource = ddm_data_model_ensure_resource(hippo_model->ddm_model,
                                                           self_id, "http://mugshot.org/p/o/user";);
-
-        /* HACK - right now the above ensure_resource does not load any properties
-         * of the self user resource, which means a fetch like "self [photoUrl]" won't
-         * work. The correct fix is to add support for that kind of fetch to the
-         * in-process data model, i.e. do the below query as-needed and automatically.
-         * 
-         * Also, FIXME since we don't really support forgetting queries right now
-         * we end up leaking this if the self id changes
-         */
-        if (hippo_model->self_id == NULL ||
-            strcmp(hippo_model->self_id, self_id) != 0) {            
-            hippo_model->self_query = ddm_data_model_query_resource(hippo_model->ddm_model,
-                                                                    self_id, "+");
-            
-            g_free(hippo_model->self_id);
-            hippo_model->self_id = g_strdup(self_id);
-        }
     } else {
         value.type = DDM_DATA_NONE;
-
-        g_free(hippo_model->self_id);
-        hippo_model->self_id = NULL;
     }
 
-    if (ddm_data_resource_update_property(global_resource,
-                                          self_id_prop,
-                                          self_id ? DDM_DATA_UPDATE_REPLACE : DDM_DATA_UPDATE_DELETE,
-                                          DDM_DATA_CARDINALITY_01,
-                                          FALSE, NULL,
-                                          &value)) {
-        GSList *changed_list;
-        changed_list = g_slist_prepend(NULL, self_id_prop);
-        ddm_data_resource_on_resource_change(global_resource, changed_list);
-        g_slist_free(changed_list);
-    }
+    ddm_data_resource_update_property(global_resource,
+                                      self_id_prop,
+                                      self_id ? DDM_DATA_UPDATE_REPLACE : DDM_DATA_UPDATE_DELETE,
+                                      DDM_DATA_CARDINALITY_01,
+                                      FALSE, NULL,
+                                      &value);
     
     ddm_data_model_set_connected(hippo_model->ddm_model, connected);
 }
@@ -175,7 +149,6 @@
     
     g_object_set_data(G_OBJECT(ddm_model), "hippo-data-model", NULL);
 
-    g_free(hippo_model->self_id);
     g_free(hippo_model);
 }
 

Modified: dumbhippo/trunk/client/linux/Makefile-libddm.am
===================================================================
--- dumbhippo/trunk/client/linux/Makefile-libddm.am	2007-11-01 22:38:17 UTC (rev 6863)
+++ dumbhippo/trunk/client/linux/Makefile-libddm.am	2007-11-01 23:17:41 UTC (rev 6864)
@@ -30,7 +30,9 @@
 libddmincludedir = $(includedir)/ddm-1/ddm
 nodist_libddminclude_HEADERS=				\
 	$(COMMONSRCDIR)/ddm/ddm.h			\
+	$(COMMONSRCDIR)/ddm/ddm-client.h		\
 	$(COMMONSRCDIR)/ddm/ddm-data-fetch.h		\
+	$(COMMONSRCDIR)/ddm/ddm-data-fetch.h		\
 	$(COMMONSRCDIR)/ddm/ddm-data-model.h		\
 	$(COMMONSRCDIR)/ddm/ddm-data-model-backend.h	\
 	$(COMMONSRCDIR)/ddm/ddm-data-model-dbus.h	\
@@ -40,6 +42,9 @@
 	$(COMMONSRCDIR)/ddm/ddm-qname.h
 
 LIBDDM_SOURCEFILES =						\
+	$(COMMONSRCDIR)/ddm/ddm-client.c			\
+	$(COMMONSRCDIR)/ddm/ddm-client-notification.c		\
+	$(COMMONSRCDIR)/ddm/ddm-client-notification.h		\
 	$(COMMONSRCDIR)/ddm/ddm-condition-parser.c		\
 	$(COMMONSRCDIR)/ddm/ddm-condition.c			\
 	$(COMMONSRCDIR)/ddm/ddm-data-fetch.c			\
@@ -50,9 +55,13 @@
 	$(COMMONSRCDIR)/ddm/ddm-data-resource-internal.h	\
 	$(COMMONSRCDIR)/ddm/ddm-data-query.c			\
 	$(COMMONSRCDIR)/ddm/ddm-data-query-internal.h		\
+	$(COMMONSRCDIR)/ddm/ddm-local-client.c			\
+	$(COMMONSRCDIR)/ddm/ddm-local-client.h			\
 	$(COMMONSRCDIR)/ddm/ddm-notification-set.c		\
 	$(COMMONSRCDIR)/ddm/ddm-rule.h				\
 	$(COMMONSRCDIR)/ddm/ddm-qname.c				\
+	$(COMMONSRCDIR)/ddm/ddm-work-item.c			\
+	$(COMMONSRCDIR)/ddm/ddm-work-item.h			\
 	$(COMMONSRCDIR)/ddm/hippo-dbus-helper.c			\
 	$(COMMONSRCDIR)/ddm/hippo-dbus-helper.h			\
 	$(COMMONSRCDIR)/ddm/hippo-dbus-helper-rename.h
@@ -90,6 +99,8 @@
 ddm_tests =					\
 	test-condition-parser			\
 	test-condition-reduce			\
+	test-multipart-fetch			\
+	test-notification			\
 	test-static-file-parser			\
 	test-static-file-backend
 
@@ -118,6 +129,16 @@
 
 nodist_test_ddm_SOURCES=$(COMMONSRCDIR)/ddm/test-ddm.c
 
+test_multipart_fetch_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDDM_CFLAGS) -DDDM_COMPILATION=1
+test_multipart_fetch_LDADD=libddm-1.la libddm-test.la
+
+nodist_test_multipart_fetch_SOURCES=$(COMMONSRCDIR)/ddm/test-multipart-fetch.c
+
+test_notification_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDDM_CFLAGS) -DDDM_COMPILATION=1
+test_notification_LDADD=libddm-1.la libddm-test.la
+
+nodist_test_notification_SOURCES=$(COMMONSRCDIR)/ddm/test-notification.c
+
 test_static_file_parser_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDDM_CFLAGS) -DDDM_COMPILATION=1
 test_static_file_parser_LDADD=libddm-1.la libddm-test.la
 



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