diff options
Diffstat (limited to 'mailnews/db/gloda/modules/index_ab.js')
-rw-r--r-- | mailnews/db/gloda/modules/index_ab.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/mailnews/db/gloda/modules/index_ab.js b/mailnews/db/gloda/modules/index_ab.js new file mode 100644 index 0000000000..299733275f --- /dev/null +++ b/mailnews/db/gloda/modules/index_ab.js @@ -0,0 +1,287 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.EXPORTED_SYMBOLS = ['GlodaABIndexer', 'GlodaABAttrs']; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource:///modules/gloda/collection.js"); +Cu.import("resource:///modules/gloda/datastore.js"); +Cu.import("resource:///modules/gloda/gloda.js"); +Cu.import("resource:///modules/gloda/indexer.js"); +Cu.import("resource:///modules/gloda/log4moz.js"); +Cu.import("resource:///modules/gloda/noun_freetag.js"); +Cu.import("resource:///modules/gloda/utils.js"); +Cu.import("resource:///modules/mailServices.js"); + + +var GlodaABIndexer = { + _log: null, + + name: "index_ab", + enable: function() { + if (this._log == null) + this._log = Log4Moz.repository.getLogger("gloda.index_ab"); + + MailServices.ab.addAddressBookListener(this, + Ci.nsIAbListener.itemAdded | + Ci.nsIAbListener.itemChanged | + Ci.nsIAbListener.directoryItemRemoved); + }, + + disable: function() { + MailServices.ab.removeAddressBookListener(this); + }, + + // it's a getter so we can reference 'this' + get workers() { + return [ + ["ab-card", { + worker: this._worker_index_card, + }], + ]; + }, + + _worker_index_card: function*(aJob, aCallbackHandle) { + let card = aJob.id; + + if (card.primaryEmail) { + // load the identity + let query = Gloda.newQuery(Gloda.NOUN_IDENTITY); + query.kind("email"); + // we currently normalize all e-mail addresses to be lowercase + query.value(card.primaryEmail.toLowerCase()); + let identityCollection = query.getCollection(aCallbackHandle); + yield Gloda.kWorkAsync; + + if (identityCollection.items.length) { + let identity = identityCollection.items[0]; + // force the identity to know it has an associated ab card. + identity._hasAddressBookCard = true; + + this._log.debug("Found identity, processing card."); + yield aCallbackHandle.pushAndGo( + Gloda.grokNounItem(identity.contact, {card: card}, false, false, + aCallbackHandle)); + this._log.debug("Done processing card."); + } + } + + yield GlodaIndexer.kWorkDone; + }, + + initialSweep: function() { + }, + + /* ------ nsIAbListener ------ */ + /** + * When an address book card is added, update the cached GlodaIdentity + * object's cached idea of whether the identity has an ab card. + */ + onItemAdded: function ab_indexer_onItemAdded(aParentDir, aItem) { + if (!(aItem instanceof Ci.nsIAbCard)) + return; + + this._log.debug("Received Card Add Notification"); + let identity = GlodaCollectionManager.cacheLookupOneByUniqueValue( + Gloda.NOUN_IDENTITY, "email@" + aItem.primaryEmail.toLowerCase()); + if (identity) + identity._hasAddressBookCard = true; + }, + /** + * When an address book card is added, update the cached GlodaIdentity + * object's cached idea of whether the identity has an ab card. + */ + onItemRemoved: function ab_indexer_onItemRemoved(aParentDir, aItem) { + if (!(aItem instanceof Ci.nsIAbCard)) + return; + + this._log.debug("Received Card Removal Notification"); + let identity = GlodaCollectionManager.cacheLookupOneByUniqueValue( + Gloda.NOUN_IDENTITY, "email@" + aItem.primaryEmail.toLowerCase()); + if (identity) + identity._hasAddressBookCard = false; + + }, + onItemPropertyChanged: function ab_indexer_onItemPropertyChanged(aItem, + aProperty, aOldValue, aNewValue) { + if (aProperty == null && aItem instanceof Ci.nsIAbCard) { + this._log.debug("Received Card Change Notification"); + + let card = aItem; // instanceof already QueryInterface'd for us. + let job = new IndexingJob("ab-card", card); + GlodaIndexer.indexJob(job); + } + } +}; +GlodaIndexer.registerIndexer(GlodaABIndexer); + +var GlodaABAttrs = { + providerName: "gloda.ab_attr", + _log: null, + + init: function() { + this._log = Log4Moz.repository.getLogger("gloda.abattrs"); + + try { + this.defineAttributes(); + } + catch (ex) { + this._log.error("Error in init: " + ex); + throw ex; + } + }, + + defineAttributes: function() { + /* ***** Contacts ***** */ + this._attrIdentityContact = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrDerived, + attributeName: "identities", + singular: false, + special: Gloda.kSpecialColumnChildren, + //specialColumnName: "contactID", + storageAttributeName: "_identities", + subjectNouns: [Gloda.NOUN_CONTACT], + objectNoun: Gloda.NOUN_IDENTITY, + }); // tested-by: test_attributes_fundamental + this._attrContactName = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrFundamental, + attributeName: "name", + singular: true, + special: Gloda.kSpecialString, + specialColumnName: "name", + subjectNouns: [Gloda.NOUN_CONTACT], + objectNoun: Gloda.NOUN_STRING, + canQuery: true, + }); // tested-by: test_attributes_fundamental + this._attrContactPopularity = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrDerived, + attributeName: "popularity", + singular: true, + special: Gloda.kSpecialColumn, + specialColumnName: "popularity", + subjectNouns: [Gloda.NOUN_CONTACT], + objectNoun: Gloda.NOUN_NUMBER, + canQuery: true, + }); // not-tested + this._attrContactFrecency = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrDerived, + attributeName: "frecency", + singular: true, + special: Gloda.kSpecialColumn, + specialColumnName: "frecency", + subjectNouns: [Gloda.NOUN_CONTACT], + objectNoun: Gloda.NOUN_NUMBER, + canQuery: true, + }); // not-tested + + /* ***** Identities ***** */ + this._attrIdentityContact = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrDerived, + attributeName: "contact", + singular: true, + special: Gloda.kSpecialColumnParent, + specialColumnName: "contactID", // the column in the db + idStorageAttributeName: "_contactID", + valueStorageAttributeName: "_contact", + subjectNouns: [Gloda.NOUN_IDENTITY], + objectNoun: Gloda.NOUN_CONTACT, + canQuery: true, + }); // tested-by: test_attributes_fundamental + this._attrIdentityKind = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrFundamental, + attributeName: "kind", + singular: true, + special: Gloda.kSpecialString, + specialColumnName: "kind", + subjectNouns: [Gloda.NOUN_IDENTITY], + objectNoun: Gloda.NOUN_STRING, + canQuery: true, + }); // tested-by: test_attributes_fundamental + this._attrIdentityValue = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrFundamental, + attributeName: "value", + singular: true, + special: Gloda.kSpecialString, + specialColumnName: "value", + subjectNouns: [Gloda.NOUN_IDENTITY], + objectNoun: Gloda.NOUN_STRING, + canQuery: true, + }); // tested-by: test_attributes_fundamental + + /* ***** Contact Meta ***** */ + // Freeform tags; not explicit like thunderbird's fundamental tags. + // we differentiate for now because of fundamental implementation + // differences. + this._attrFreeTag = Gloda.defineAttribute({ + provider: this, + extensionName: Gloda.BUILT_IN, + attributeType: Gloda.kAttrExplicit, + attributeName: "freetag", + bind: true, + bindName: "freeTags", + singular: false, + subjectNouns: [Gloda.NOUN_CONTACT], + objectNoun: Gloda.lookupNoun("freetag"), + parameterNoun: null, + canQuery: true, + }); // not-tested + // we need to find any existing bound freetag attributes, and use them to + // populate to FreeTagNoun's understanding + if ("parameterBindings" in this._attrFreeTag) { + for (let freeTagName in this._attrFreeTag.parameterBindings) { + this._log.debug("Telling FreeTagNoun about: " + freeTagName); + FreeTagNoun.getFreeTag(freeTagName); + } + } + }, + + process: function*(aContact, aRawReps, aIsNew, aCallbackHandle) { + let card = aRawReps.card; + if (aContact.NOUN_ID != Gloda.NOUN_CONTACT) { + this._log.warn("Somehow got a non-contact: " + aContact); + return; // this will produce an exception; we like. + } + + // update the name + if (card.displayName && card.displayName != aContact.name) + aContact.name = card.displayName; + + aContact.freeTags = []; + + let tags = null; + try { + tags = card.getProperty("Categories", null); + } catch (ex) { + this._log.error("Problem accessing property: " + ex); + } + if (tags) { + for (let tagName of tags.split(",")) { + tagName = tagName.trim(); + if (tagName) { + aContact.freeTags.push(FreeTagNoun.getFreeTag(tagName)); + } + } + } + + yield Gloda.kWorkDone; + } +}; |