[Checkins] SVN: Sandbox/J1m/dozodb/src/zc/dozodb/ Began fleshing out save support.

Jim Fulton jim at zope.com
Wed Dec 29 16:24:28 EST 2010


Log message for revision 119212:
  Began fleshing out save support.
  

Changed:
  U   Sandbox/J1m/dozodb/src/zc/dozodb/__init__.py
  U   Sandbox/J1m/dozodb/src/zc/dozodb/dozodb.js

-=-
Modified: Sandbox/J1m/dozodb/src/zc/dozodb/__init__.py
===================================================================
--- Sandbox/J1m/dozodb/src/zc/dozodb/__init__.py	2010-12-29 21:24:26 UTC (rev 119211)
+++ Sandbox/J1m/dozodb/src/zc/dozodb/__init__.py	2010-12-29 21:24:28 UTC (rev 119212)
@@ -1,4 +1,8 @@
-import json
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
 import persistent
 
 class Persistent(persistent.Persistent):
@@ -6,13 +10,21 @@
     def _dozodb_get_client_state(self):
         return self.__getstate__()
 
+    def _dozodb_set_client_state(self, state):
+        self.__setstate__(state)
+
+    @classmethod
+    def _dozodb_new(class_):
+        return class_.__new__(class_)
+
 class Encoder(json.JSONEncoder):
     def default(self, obj):
         if isinstance(obj, persistent.Persistent):
-            if not hasattr(obj._dozodb_get_client_state):
+            if not hasattr(obj, '_dozodb_get_client_state'):
                 raise TypeError(
                     "Object doesn't support client serialization",
                     obj)
+            # XXX maybe we should use _p_ref here. Would need js change
             return dict(_p_oid=obj._p_oid.encode('hex'))
         return json.JSONEncoder.default(self, obj)
 
@@ -22,17 +34,109 @@
 def _serialize(ob):
     state = ob._dozodb_get_client_state()
     state.update(dict(
-        _p_oid = self._p_oid.encode('hex'),
-        _p_serial = self._p_serial.encode('hex'),
+        _p_oid = ob._p_oid.encode('hex'),
+        _p_serial = ob._p_serial.encode('hex'),
         ))
     return state
 
 def load(connection, _p_oid):
-    return result(
-        item=_serialize(c.get(_p_oid.decode('hex')))
+    return _result(
+        item=_serialize(connection.get(_p_oid.decode('hex')))
         )
 
 def fetched(items):
-    return result(items=[_serialize(item) for item in items])
+    return _result(items=[_serialize(item) for item in items])
 
+otypes = list, dict
+def save(app, json_string):
 
+    #import pdb; pdb.set_trace()
+
+    changes = json.loads(json_string)
+
+    changed = dict((item['_p_oid'], item) for item in changes['changed'])
+    updated = [oid for oid in changed if not oid.startswith('new')]
+    inserted = changes['inserted']
+
+    new_ids = {}
+
+    def cleanup(data):
+        if isinstance(data, list):
+            return map(cleanup, data)
+        if not isinstance(data, dict):
+            return data
+
+        ref = data.get('_p_ref')
+        if ref:
+            item = changed.pop(ref, None)
+            if item is not None:
+                return cleanup(item)
+            oid = new_ids.get(ref) or ref.decode('hex')
+            return app.connection.get(oid)
+
+        oid = data.pop('_p_oid')
+        if oid:
+            changed.pop(oid, None)
+            if oid.startswith('new'):
+                ob = app.factory(data)._dozodb_new()
+                app.connection.add(ob)
+                new_ids[oid] = ob._p_oid
+            else:
+                oid = oid.decode('hex')
+                ob = app.connection.get(oid)
+                ob._p_activate()
+                ob._p_changed = 1
+                ob._p_serial = data.pop('_p_serial').decode('hex')
+
+        for name, value in data.items():
+            if isinstance(value, otypes):
+                value = cleanup(value)
+                data[name] = value
+
+        if oid:
+            ob._dozodb_set_client_state(data)
+            return ob
+
+        return data
+
+    for oid in updated + inserted:
+        if oid in changed:
+            cleanup(changed[oid])
+
+    if changed:
+        raise ValueError("Unreachable new objects")
+
+    if inserted:
+        app.insert(inserted)
+
+    # XXX it's annoying that we have to commit here, but we need the
+    # committed serials. :/  Maybe we can think of something better
+    # later, especially when we deal with invalidations.
+    app.connection.transaction_manager.commit()
+
+    updates = []
+    for (old, oid) in new_ids.iteritems():
+        ob = app.connection.get(oid)
+        ob._p_activate()
+        updates.append(dict(_p_oid=oid.encode('hex'), _p_id=old,
+                            _p_serial=ob._p_serial.encode('hex')
+                            ))
+    for oid in updated:
+        ob = app.connection.get(oid.decode('hex'))
+        ob._p_activate()
+        updates.append(dict(_p_oid=oid,
+                            _p_serial=ob._p_serial.encode('hex')
+                            ))
+
+    return json.dumps(dict(updates=updates))
+
+
+def webob(app, request):
+    if request.method == 'GET':
+        if '_p_oid' in request.str_GET:
+            return load(app.connection, request.str_GET.get('_p_oid'))
+        return fetched(app.query())
+
+    assert(request.method == 'POST')
+    assert request.content_type.startswith('application/json')
+    return save(app, request.body)

Modified: Sandbox/J1m/dozodb/src/zc/dozodb/dozodb.js
===================================================================
--- Sandbox/J1m/dozodb/src/zc/dozodb/dozodb.js	2010-12-29 21:24:26 UTC (rev 119211)
+++ Sandbox/J1m/dozodb/src/zc/dozodb/dozodb.js	2010-12-29 21:24:28 UTC (rev 119212)
@@ -16,6 +16,16 @@
     };
     Request = dojo.declare(null, Request);
 
+    var idattrs = {_p_oid: true, _p_id: true};
+
+    function list(obj) {
+        var r = [];
+        for (var name in obj)
+            if (obj.hasOwnProperty(name))
+                r.push(name);
+        return r;
+    }
+
     var Dozodb = {
 
         // url: url of data server
@@ -24,10 +34,14 @@
             dojo.safeMixin(this, args);
             this._cache = {};       // {oid->ob}
             this._new_next = 0;
-            this._new_ids = {};
+            this._initialize_changes();
         },
 
-        _fixupItemSubobjects: function (item) {
+        _initialize_changes: function () {
+            this._changes = {changed: {}, inserted: {}};
+        },
+
+        _convert_incoming_items_refs_to_items: function (item) {
             for (var name in item) {
                 var v = item[name];
                 if (typeof(v) != "object")
@@ -41,22 +55,60 @@
                 }
                 if (oid)
                     item[name] = this._in_cache(v);
-                this._fixupItemSubobjects(v);
+                this._convert_incoming_items_refs_to_items(v);
             }
         },
 
+        _convert_outgoing_items_to_item_refs: function (item, isobject) {
+            var maybe_array = (! isobject) && (item.length != null);
+            var result;
+            if (maybe_array)
+                result = [];
+            else
+                result = {};
+
+            for (var name in item) {
+                if (! item.hasOwnProperty(name))
+                    continue;
+
+                if (maybe_array && name == 'length')
+                    return _convert_outgoing_items_to_item_refs(item, true);
+
+                var v = item[name];
+                if (typeof(v) != "object") {
+                    result[name] = v;
+                    continue;
+                }
+                var oid;
+                try {
+                    oid = v._p_id;
+                    }
+                catch (e) {
+                    oid = null;
+                }
+                if (oid) {
+                    result[name] = {_p_ref: oid};
+                    continue;
+                }
+                result[name] = this._convert_outgoing_items_to_item_refs(v);
+            }
+            return result;
+        },
+
         _in_cache: function (item, with_data) {
-            if (item._p_oid in this._cache) {
-                var cached = this._cache[item._p_oid];
+            if (! item._p_id)
+                item._p_id = item._p_oid;
+            if (item._p_id in this._cache) {
+                var cached = this._cache[item._p_id];
                 if (with_data)
                     dojo.safeMixin(cached, item);
                 item = cached;
             }
             else
-                this._cache[item._p_oid] = item;
+                this._cache[item._p_id] = item;
 
             if (with_data) {
-                this._fixupItemSubobjects(item);
+                this._convert_incoming_items_refs_to_items(item);
                 item._p_changed = false;
             }
             return item;
@@ -72,11 +124,19 @@
                 return false;
             if (v.length == null)
                 return v == value;
-            return dojo.indexOf(v, value) >= 0;
+            for (var i=v.length; --i >= 0; )
+                if (v[i] === value)
+                    return true;
+            return false;
         },
 
         deleteItem : function (item) {
-            // xxx
+            if (! item._p_oid) {
+                // New object
+                delete this._changes.changed[item._p_id];
+                if (item._p_id in this._changes.inserted)
+                    delete this._changes.inserted[item._p_id];
+            }
             this.onDelete(item);
         },
         onDelete: function (item) {}, // hook
@@ -88,7 +148,7 @@
                 {
                     // Server gets query args for args.query
                     // Server returns: {items: array_of_items}
-                    url: self.url+'/fetch',
+                    url: self.url,
                     handleAs: 'json',
                     preventCache: true,
                     content: args.query,
@@ -147,7 +207,7 @@
                 {
                     // Server gets query arg _p_oid
                     // Server returns: {item: item}
-                    url: this.url+'/fetchBydentity',
+                    url: this.url,
                     handleAs: 'json',
                     preventCache: true,
                     content: {_p_oid: args.identity},
@@ -179,16 +239,16 @@
         },
 
         getIdentity: function (item) {
-            return item._p_oid;
+            return item._p_id;
         },
 
         getIdentityAttributes: function (item) {
-            return ['_p_oid'];
+            return ['_p_id'];
         },
 
         getLabel : function (item) {
             // replace me :)
-            return item.title || item.label || item._p_oid || 'unlabeled';
+            return item.title || item.label || item._p_id || 'unlabeled';
         },
 
         getValue : function (item, attribute, defaultValue) {
@@ -233,14 +293,14 @@
                 {
                     // Server gets query arg _p_oid
                     // Server returns: {item: item}
-                    url: this.url+'/load',
+                    url: this.url,
                     handleAs: 'json',
                     preventCache: true,
                     content: {_p_oid: item._p_oid},
                     load: function (r) {
                         dojo.safeMixin(item, r.item);
                         item._p_changed = false;
-                        self._fixupItemSubobjects(item);
+                        self._convert_incoming_items_refs_to_items(item);
                         if (args.onItem)
                             dojo.hitch(args.scope, args.onItem)(item);
                     },
@@ -252,32 +312,95 @@
 
         newItem : function (args, parentInfo) {
             var item = dojo.safeMixin(
-                {_p_oid: 'new'+this._new_next++, _p_changed: 0}, args);
+                {_p_id: 'new'+this._new_next++, _p_changed: 1}, args);
             if (parentInfo) {
-                var v = parentInfo.parent[parentInfo.attribute];
+                var parent = parentInfo.parent;
+                if (item._p_changed == null)
+                    throw("Attempt to modify ghost parent.");
+                var v = parent[parentInfo.attribute];
                 if (v == null || v.length == null)
-                    parentInfo.parent[parentInfo.attribute] = item;
+                    parent[parentInfo.attribute] = item;
                 else
                     v.push(item);
+                this._changes.changed[parent._p_id] = parent;
             }
-            this.onNew(
-                item, {
-                    item: parentInfo.parent,
-                    attribute: parentInfo.attribute
-                });
+            else
+                this._changes.inserted[item._p_id] = 1;
+            this._cache[item._p_id] = item;
+            this._changes.changed[item._p_id] = item;
+            if (parentInfo)
+                this.onNew(
+                    item, { item: parent, attribute: parentInfo.attribute });
+            else
+                this.onNew(item);
             return item;
         },
         onNew: function (item, parentInfo) {}, // hook
 
         revert : function () {
+            for (var id in self._changes.changed) {
+                var item = self._changes.changed[id];
+                if (item._p_oid) {
+                    var _p_oid = item._p_oid;
+                    for (var name in item)
+                        if (item.hasOwnProperty(name) && ! name in ids)
+                            delete item[name];
+                }
+                else
+                {
+                    delete self._cache[id];
+                }
+            }
+            self._initialize_changes();
+        },
 
+        _to_save: function () {
+            self = this;
+            return dojo.toJson(
+                {
+                    changed: dojo.map(
+                        list(self._changes.changed),
+                        function (id) {
+                            var r = self._convert_outgoing_items_to_item_refs(
+                                self._changes.changed[id], true);
+                            r._p_oid = r._p_id;
+                            delete r._p_id;
+                            delete r._p_changed;
+                            return r;
+                        }),
+                    inserted: list(self._changes.inserted)
+                });
         },
 
         save : function (args) {
-
+            self = this;
+            dojo.xhrPost(
+                {
+                    url: this.url,
+                    handleAs: 'json',
+                    headers: {'Content-Type': 'application/json'},
+                    postData: self._to_save(),
+                    load: function (r) {
+                        self._initialize_changes();
+                        for (var i=r.updates.length; --i >= 0; ) {
+                            var data = r.updates[i];
+                            if ('_p_id' in data) {
+                                self._cache[data._p_oid] = self._cache[
+                                    data._p_id];
+                                delete data._p_id;
+                            }
+                            var item = self._cache[data._p_oid];
+                            for (var name in data)
+                                if (data.hasOwnProperty(name))
+                                    item[name] = data[name];
+                        }
+                    }
+                });
         },
 
         setValue : function (item, attribute, value) {
+            if (item._p_changed == null)
+                throw("Attempt to modify ghost.");
             var old = undefined;
             if (attribute in item)
                 old = attribute[item];
@@ -285,10 +408,13 @@
                 item[attribute] = value;
             else
                 item[attribute] = [value];
+            this._changes.changed[item._p_id] = item;
             this.onSet(item, attribute, old, value);
         },
 
         setValues : function (item, attribute, value) {
+            if (item._p_changed == null)
+                throw("Attempt to modify ghost.");
             var old = undefined;
             if (attribute in item)
                 old = item[attribute];
@@ -296,6 +422,7 @@
                 delete item[attribute];
             else
                 item[attribute] = value;
+            this._changes.changed[item._p_id] = item;
             this.onSet(item, attribute, old, value);
         },
         onSet: function (item, attribute, old, new_) {}, // hook
@@ -306,6 +433,6 @@
     };
 
     return {
-        DB: dojo.declare(null, Dozodb)
+        Store: dojo.declare(null, Dozodb)
     };
 }();



More information about the checkins mailing list