status commit, trivial visualization snapshot, with interesting gloda back-end
authorAndrew Sutherland <asutherland@asutherland.org>
Tue, 08 Jul 2008 17:37:47 -0700
changeset 836 9f65871c06614dbec00b278cd60321713c01e028
parent 835 0947fdaeffb94975b0dad05b93021951bb9b0653
child 837 0f94c6fcee7f23f215ab00d48ad58344dc2784aa
push idunknown
push userunknown
push dateunknown
status commit, trivial visualization snapshot, with interesting gloda back-end changes being support for bound attributes with caching.
modules/datamodel.js
modules/datastore.js
modules/explattr.js
modules/fundattr.js
modules/gloda.js
--- a/modules/datamodel.js
+++ b/modules/datamodel.js
@@ -31,17 +31,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 EXPORTED_SYMBOLS = ["GlodaAttributeDef", "GlodaConversation", "GlodaMessage",
-                    "GlodaContact", "GlodaIdentity"];
+                    "GlodaContact", "GlodaIdentity", "GlodaTag"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gloda/modules/log4moz.js");
 const LOG = Log4Moz.Service.getLogger("gloda.datamodel");
@@ -242,8 +242,15 @@ GlodaIdentity.prototype = {
   
   get contact() {
     if (this._contact == null) {
       this._contact = this._datastore.getContactByID(this._contactID);
     }
     return this._contact;
   },
 };
+
+function GlodaTag() {
+}
+
+GlodaTag.prototype = {
+};
+
--- a/modules/datastore.js
+++ b/modules/datastore.js
@@ -603,16 +603,37 @@ let GlodaDatastore = {
     return new GlodaMessage(this, aRow["id"], aRow["folderID"],
                             this._mapFolderID(aRow["folderID"]),
                             aRow["messageKey"],
                             aRow["conversationID"], null,
                             aRow["parentID"],
                             aRow["headerMessageID"], aRow["bodySnippet"]);
   },
 
+  get _selectMessageByIDStatement() {
+    let statement = this._createStatement(
+      "SELECT * FROM messages WHERE id = :id");
+    this.__defineGetter__("_selectMessageByIDStatement",
+      function() statement);
+    return this._selectMessageByIDStatement;
+  },
+
+  getMessageByID: function gloda_ds_getMessageByID(aID) {
+    let message = null;
+  
+    let smbis = this._selectMessageByIDStatement;
+    
+    smbis.params.id = aID;
+    if (smbis.step())
+      message = this._messageFromRow(smbis.row);
+    smbis.reset();
+    
+    return message;
+  },
+
   get _selectMessageByLocationStatement() {
     let statement = this._createStatement(
       "SELECT * FROM messages WHERE folderID = :folderID AND \
                                     messageKey = :messageKey");
     this.__defineGetter__("_selectMessageByLocationStatement",
       function() statement);
     return this._selectMessageByLocationStatement;
   },
@@ -916,9 +937,31 @@ let GlodaDatastore = {
     ibkv.params.value = aValue;
     if (ibkv.step()) {
       identity = this._identityFromRow(ibkv.row);
     }
     ibkv.reset();
     
     return identity;
   },
+
+  get _selectIdentityByIDStatement() {
+    let statement = this._createStatement(
+      "SELECT * FROM identities WHERE id :id");
+    this.__defineGetter__("_selectIdentityByIDStatement",
+      function() statement);
+    return this._selectIdentityByIDStatement;
+  },
+
+  getIdentityByID: function gloda_ds_getIdentity(aID) {
+    let identity = null;
+    
+    let sibis = this._selectIdentityByIDStatement;
+    sibis.params.id = aID;
+    if (sibis.step()) {
+      sibis = this._identityFromRow(sibis.row);
+    }
+    sibis.reset();
+    
+    return identity;
+  },
+
 };
--- a/modules/explattr.js
+++ b/modules/explattr.js
@@ -76,27 +76,27 @@ let GlodaExplicitAttr = {
 
   _attrTag: null,
   _attrStar: null,
   _attrRead: null,
   
   defineAttributes: function() {
     // Tag
     this._attrTag = Gloda.defineAttr(this, Gloda.kAttrExplicit,
-                        Gloda.BUILT_IN, FA_TAG,
+                        Gloda.BUILT_IN, FA_TAG, Gloda.kMultiple,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_DATE, Gloda.NOUN_TAG,
                         "%{subject} was tagged %{parameter} on %{object}");
     // Star
     this._attrStar = Gloda.defineAttr(this, Gloda.kAttrExplicit,
-                        Gloda.BUILT_IN, FA_STAR,
+                        Gloda.BUILT_IN, FA_STAR, Gloda.kSingular,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_BOOLEAN, null,
                         "%{subject} has a star state of %{object}");
     // Read/Unread
     this._attrRead = Gloda.defineAttr(this, Gloda.kAttrExplicit,
-                        Gloda.BUILT_IN, FA_READ,
+                        Gloda.BUILT_IN, FA_READ, Gloda.kSingular,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_BOOLEAN, null,
                         "%{subject} has a read state of %{object}");
     
   },
   
   process: function Gloda_explattr_process(aGlodaMessage, aMsgHdr) {
     let attribs = [];
     
--- a/modules/fundattr.js
+++ b/modules/fundattr.js
@@ -78,33 +78,37 @@ let GlodaFundAttr = {
   _attrFrom: null,
   _attrTo: null,
   _attrCc: null,
   _attrDate: null,
   
   defineAttributes: function() {
     // From
     this._attrFrom = Gloda.defineAttr(this, Gloda.kAttrFundamental,
-                        Gloda.BUILT_IN, FA_FROM,
+                        Gloda.BUILT_IN, FA_FROM, Gloda.kSingular,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_IDENTITY, null,
+                        "from",
                         "%{subject} was sent by %{object}");
     // To
     this._attrTo = Gloda.defineAttr(this, Gloda.kAttrFundamental,
-                        Gloda.BUILT_IN, FA_TO,
+                        Gloda.BUILT_IN, FA_TO, Gloda.kMultiple,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_IDENTITY, null,
+                        "to",
                         "%{subject} was sent to %{object}");
     // Cc
     this._attrCc = Gloda.defineAttr(this, Gloda.kAttrFundamental,
-                        Gloda.BUILT_IN, FA_CC,
+                        Gloda.BUILT_IN, FA_CC, Gloda.kMultiple,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_IDENTITY, null,
+                        "cc",
                         "%{subject} was carbon-copied to %{object}");
     // Date
     this._attrDate = Gloda.defineAttr(this, Gloda.kAttrFundamental,
-                        Gloda.BUILT_IN, FA_DATE,
+                        Gloda.BUILT_IN, FA_DATE, Gloda.kSingular,
                         Gloda.NOUN_MESSAGE, Gloda.NOUN_DATE, null,
+                        "date",
                         "%{subject} was sent on %{object}");
     
   },
   
   process: function gloda_fundattr_process(aGlodaMessage, aMsgHdr) {
     let attribs = [];
     
     // -- From
--- a/modules/gloda.js
+++ b/modules/gloda.js
@@ -131,48 +131,137 @@ let Gloda = {
   },
   
   kAttrFundamental: 0,
   kAttrOptimization: 1,
   kAttrDerived: 2,
   kAttrExplicit: 3,
   kAttrImplicit: 4,
   
+  kSingular: 0,
+  kMultiple: 1,
+  
   BUILT_IN: "built-in",
   
+  NOUN_BOOLEAN: 1,
   /** A date, encoded as a PRTime */
   NOUN_DATE: 10,
   NOUN_TAG: 50,
   NOUN_CONVERSATION: 101,
   NOUN_MESSAGE: 102,
   NOUN_CONTACT: 103,
   NOUN_IDENTITY: 104,
   
   /** Attribute providers in the sequence to process them. */
   _attrProviderOrder: [],
   /** Maps attribute providers to the list of attributes they provide */
   _attrProviders: {},
   
+  _nounToClass: {},
+  
   _initAttributes: function gloda_ns_initAttributes() {
+    this._nounToClass[this.NOUN_BOOLEAN] = {class: Boolean,
+      coerce: function(aVal) { if(aVal != 0) return true; else return false; }}; 
+    this._nounToClass[this.NOUN_DATE] = {class: Date,
+      coerce: function(aPRTime) {return new Date(aPRTime / 1000); }};
+
+    // TODO: implement GlodaTag or some other abstraction 
+    this._nounToClass[this.NOUN_TAG] = {class: GlodaTag,
+      coerce: null};
+       
+    // TODO: use some form of (weak) caching layer... it is reasonably likely
+    //  that there will be a high degree of correlation in many cases, and
+    //  unless the UI is extremely clever and does its cleverness before
+    //  examining the data, we will probably hit the correlation.
+    this._nounToClass[this.NOUN_CONVERSATION] = {class: GlodaConversation,
+      coerce: function(aID) { return GlodaDatastore.getConversationByID(aID);}};
+    this._nounToClass[this.NOUN_MESSAGE] = {class: GlodaMessage,
+      coerce: function(aID) { return GlodaDatastore.getMessageByID(aID); }};
+    this._nounToClass[this.NOUN_CONTACT] = {class: GlodaContact,
+      coerce: function(aID) { return GlodaDatastore.getContactByID(aID); }};
+    this._nounToClass[this.NOUN_IDENTITY] = {class: GlodaIdentity,
+      coerce: function(aID) { return GlodaDatastore.getIdentityByID(aID); }};
+  
     GlodaDatastore.getAllAttributes();
   },
   
+  
+  _bindAttribute: function gloda_ns_bindAttr(aAttr, aSubjectType, aObjectType,
+                                             aSingular, aBindName) {
+    if (!(aSubjectType in this._nounToClass))
+      throw Error("Invalid subject type: " + aSubjectType);
+    
+    let objectCoerce = this._nounToClass[aObjectType].coerce;
+    
+    let storageName = "__" + aBindName;
+    let getter;
+    // should we memoize the value as a getter per-instance?
+    if (aSingular == Gloda.kSingular) {
+      getter = function() {
+        if (this[storageName] != undefined)
+          return this[storageName];
+        let instances = this.getAttributeInstances(aAttr);
+        let val;
+        if (instances.length > 0)
+          val = objectCoerce(instances[0][2]);
+        else
+          val = null;
+        this[storageName] = val;
+        return val;
+      }
+    } else {
+      getter = function() {
+        if (this[storageName] != undefined)
+          return this[storageName];
+        let instances = this.getAttributeInstances(aAttr);
+        let values;
+        if (instances.length > 0) {
+          for (let iInst=0; iInst < instances.length; iInst++) {
+            values.push(objectCoerce(instances[iInst][2]));
+          }
+        }
+        else {
+          values = instances; // empty is empty
+        }
+        this[storageName] = values;
+        return values;
+      }
+    }
+  
+    let subjectProto = this._nounToClass[aSubjectType].class.prototype;
+    subjectProto.__defineGetter__(aBindName, getter);
+    // no setters for now; manipulation comes later, and will require the attr
+    //  definer to provide the actual logic, since we need to affect reality,
+    //  not just the data-store.  we may also just punt that all off onto
+    //  STEEL...
+  },
+  
   /**
    * @param aProvider
    * @param aAttrType
    * @param aPluginName
    * @param aAttrName
+   * @param aSingular Is the attribute going to happen at most once (kSingular),
+   *     or potentially multiple times (kMultiple).  This affects whether
+   *     the binding (as defined by aBindName) returns a list or just a single
+   *     item.
    * @param aSubjectType
    * @param aObjectType
+   * @param aBindName The name to which to bind the attribute on the underlying
+   *     data model object.  For example, for an aObjectType of NOUN_MESSAGE
+   *     with an aBindName of "date", we will create a getter on GlodaMessage so
+   *     that message.date returns the value of the date attribute (with the
+   *     specific return type depending on what was passed for 
    * @param aParameterType
    */
   defineAttr: function gloda_ns_defineAttr(aProvider, aAttrType,
-                                           aPluginName, aAttrName,
+                                           aPluginName, aAttrName, aSingular,
                                            aSubjectType, aObjectType,
                                            aParameterType,
+                                           aBindName,
                                            aExplanationFormat) {
     // provider tracking
     if (!(aProvider in this._attrProviders)) {
       this._attrProviderOrder.push(aProvider);
       this._attrProviders[aProvider] = [];
     } 
     
     let compoundName = aPluginName + ":" + aAttrName;
@@ -189,16 +278,19 @@ let Gloda = {
       
       // we are behind the abstraction veil and can set these things
       attr._provider = aProvider;
       attr._subjectType = aSubjectType;
       attr._objectType = aObjectType;
       attr._parameterType = aParameterType;
       attr._explanationFormat = aExplanationFormat;
       
+      this._bindAttribute(attr, aSubjectType, aObjectType, aSingular,
+                          aBindName);
+      
       this._attrProviders[aProvider].push(attr);
       return attr; 
     }
     
     // Being here means the attribute def does not exist in the database.
     // Of course, we only want to create something in the database if the
     //  parameter is forever un-bound (type is null).
     let attrID = null;
@@ -207,16 +299,19 @@ let Gloda = {
                                                   aAttrName, null);
     }
     
     attr = new GlodaAttributeDef(GlodaDatastore, attrID, compoundName,
                                  aProvider, aAttrType, aPluginName, aAttrName,
                                  aSubjectType, aObjectType, aParameterType,
                                  aExplanationFormat);
     GlodaDatastore._attributes[compoundName] = attr;
+
+    this._bindAttribute(attr, aSubjectType, aObjectType, aSingular, aBindName);
+
     this._attrProviders[aProvider].push(attr);
     if (aParameterType == null)    
       GlodaDatastore._attributeIDToDef[attrID] = [attr, null];
     return attr;
   },
   
   getAttrDef: function gloda_ns_getAttrDef(aPluginName, aAttrName) {
     let compoundName = aPluginName + ":" + aAttrName;