status commit, trivial visualization snapshot, with interesting gloda back-end
changes being support for bound attributes with caching.
--- 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;