/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is edsintegration. * * The Initial Developer of the Original Code is * Mozilla Corp. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Mike Conley * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * 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 ***** */ var EXPORTED_SYMBOLS = [ "nsAbEDSCard" ]; const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; Cu.import("resource://gre/modules/ctypes.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/mailServices.js"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/iteratorUtils.jsm"); Cu.import("resource://edsintegration/LibGLib.jsm"); Cu.import("resource://edsintegration/LibEContact.jsm"); Cu.import("resource://edsintegration/LibEBookClient.jsm"); Cu.import("resource://edsintegration/LibEVCard.jsm"); Cu.import("resource://edsintegration/nsAbEDSPhone.jsm"); Cu.import("resource://edsintegration/nsAbEDSIMAccount.jsm"); Cu.import("resource://edsintegration/nsAbEDSCommon.jsm"); Cu.import("resource://edsintegration/nsAbSimpleProperty.jsm"); Cu.import("resource://edsintegration/EDSFieldMappers.jsm"); Cu.import("resource://edsintegration/LibGAsyncResult.jsm"); Cu.import("resource://edsintegration/nsAbEDSEmailAddress.jsm"); function modifyContactCb(aGObject, aAsyncResult, aData) { LOG("Within modifyContactCb"); let uri = ctypes.cast(aData, LibGLib.gchar.ptr).readString(); if (!gEContactLookup[uri]) { ERROR("Could not find an nsAbEDSCard for contact with uri: " + uri); return; } let card = gEContactLookup[uri]; let errPtr = new LibGLib.GError.ptr(); let result = LibEBookClient.modifyContactFinish(card._EBookClient, aAsyncResult, errPtr.address()); if (!result) { ERROR("Could not modify EContact - message was: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; } } var modifyContactCbPtr = LibGAsyncResult.GAsyncReadyCallback(modifyContactCb); function nsAbEDSCard(aParentDir, aEBook, aEContact) { // Add a reference to aEBook and aEContact so that GLib // doesn't swallow it up. let data = LibGLib.g_object_ref(aEBook); this._parent = aParentDir; this._EBookClient = ctypes.cast(data, LibEBookClient.EBookClient.ptr); this._exists = false; this._uri = null; this._uriPtr = null; if (aEContact) { data = LibGLib.g_object_ref(aEContact); this._eContact = LibEContact.castPtr(data); this._makeExist(); } else { // We're creating a brand new contact. this._eContact = LibEContact.newEContact(); } this._dirName = ""; this._initialized = false; this._phoneNumbers = null; this._IMAccounts = null; this._emailAddrs = null; this._mappers = [ new emailMapper(this), new simpleStringMapper(this._eContact), new nameMapper(this._eContact, "E_CONTACT_NAME", "LastName", "FirstName"), new simpleBooleanMapper(this._eContact, "E_CONTACT_WANTS_HTML", "PreferMailFormat", Ci.nsIAbPreferMailFormat.html.toString(), Ci.nsIAbPreferMailFormat.unknown.toString()), new addressMapper(this._eContact, "E_CONTACT_ADDRESS_HOME", "HomeAddress", "HomeAddress2", "HomeCity", "HomeState", "HomeZipCode", "HomeCountry", "HomePOBox"), new addressMapper(this._eContact, "E_CONTACT_ADDRESS_WORK", "WorkAddress", "WorkAddress2", "WorkCity", "WorkState", "WorkZipCode", "WorkCountry", "WorkPOBox"), new addressMapper(this._eContact, "E_CONTACT_ADDRESS_OTHER", "OtherAddress", "OtherAddress2", "OtherCity", "OtherState", "OtherZipCode", "OtherCountry", "OtherPOBox"), new dateMapper(this._eContact, "E_CONTACT_BIRTH_DATE", "BirthYear", "BirthMonth", "BirthDay"), new dateMapper(this._eContact, "E_CONTACT_ANNIVERSARY", "AnniversaryYear", "AnniversaryMonth", "AnniversaryDay"), new photoMapper(this._eContact) ]; } nsAbEDSCard.prototype = { classDescription: "Evolution Data Server Address Book Contact", QueryInterface: XPCOMUtils.generateQI([Ci.nsIAbEDSCard, Ci.nsIAbCard]), _eVCardPropMap: { "E_CONTACT_PHONE_ASSISTANT": { type_1: LibEVCard.EVC_X_ASSISTANT, type_2: null }, "E_CONTACT_PHONE_BUSINESS": { type_1: "WORK", type_2: "VOICE" }, "E_CONTACT_PHONE_BUSINESS_FAX": { type_1: "WORK", type_2: "FAX" }, "E_CONTACT_PHONE_CALLBACK": { type_1: LibEVCard.EVC_X_CALLBACK, type_2: null }, "E_CONTACT_PHONE_CAR": { type_1: "CAR", type_2: null }, "E_CONTACT_PHONE_COMPANY": { type_1: "X-EVOLUTION-COMPANY", type_2: null }, "E_CONTACT_PHONE_HOME": { type_1: "HOME", type_2: "VOICE" }, "E_CONTACT_PHONE_HOME_FAX": { type_1: "HOME", type_2: "FAX" }, "E_CONTACT_PHONE_ISDN": { type_1: "ISDN", type_2: null }, "E_CONTACT_PHONE_MOBILE": { type_1: "CELL", type_2: null }, "E_CONTACT_PHONE_OTHER": { type_1: "VOICE", type_2: null }, "E_CONTACT_PHONE_OTHER_FAX": { type_1: "FAX", type_2: null }, "E_CONTACT_PHONE_PAGER": { type_1: "PAGER", type_2: null }, "E_CONTACT_PHONE_PRIMARY": { type_1: "PREF", type_2: null }, "E_CONTACT_PHONE_RADIO": { type_1: LibEVCard.EVC_X_RADIO, type_2: null }, "E_CONTACT_PHONE_TELEX": { type_1: LibEVCard.EVC_X_TELEX, type_2: null }, "E_CONTACT_PHONE_TTYTDD": { type_1: LibEVCard.EVC_X_TTYTDD, type_2: null } }, _eVCardIMServices: [ "E_CONTACT_IM_AIM", "E_CONTACT_IM_JABBER", "E_CONTACT_IM_YAHOO", "E_CONTACT_IM_GADUGADU", "E_CONTACT_IM_MSN", "E_CONTACT_IM_ICQ", "E_CONTACT_IM_GROUPWISE", "E_CONTACT_IM_SKYPE" ], _emailAddressTypes: [ LibEVCard.WORK, LibEVCard.HOME, LibEVCard.OTHER ], EMAIL_SLOTS: 4, PHONE_SLOTS: 8, IM_SLOTS: 4, get uuid() { return this._uri; }, get parentDirectory() { return this._parent; }, get EContact() { return this._eContact; }, get properties() { // This is used by nsAbCardProperty::Copy, for copying nsIAbCard's // around. // // Go through each item in the property map, preparing // an nsIProperty. var result = {}; for (let mapper in fixIterator(this._mappers)) { var clonedMap = mapper.cloneMap(); for (let key in clonedMap) { var propValue = clonedMap[key]; var prop = new nsAbSimpleProperty(key, propValue); result[key] = prop; } } return CreateSimpleObjectEnumerator(result); }, get isMailList() { return false; }, get firstName() { return this.getProperty("FirstName", ""); }, get lastName() { return this.getProperty("LastName", ""); }, get displayName() { return this.getProperty("DisplayName", ""); }, get primaryEmail() { return this.getProperty("PrimaryEmail", ""); }, get directoryId() { return ""; }, set directoryId(aValue) { }, get localId() { return this._uri; }, set localId(aValue) { }, get mailListURI() { return ""; }, set mailListURI(aValue) { }, getProperty: function EDSCard_getProperty(aKey, aDefaultValue) { for (let mapper in fixIterator(this._mappers)) { if (mapper.readsKey(aKey)) { let result = mapper.read(aKey); if (result == null) return aDefaultValue; else return result; } } return aDefaultValue; }, setProperty: function EDSCard_setProperty(aKey, aValue) { LOG("Setting property: " + aKey + " to " + aValue); let oldValue = this.getProperty(aKey, ""); for (let mapper in fixIterator(this._mappers)) { if (mapper.readsKey(aKey)) { let result = mapper.write(aKey, aValue); if (!result) ERROR("Could not save nsIAbEDSCard property: " + aKey + " --> " + aValue); else { MailServices.ab.notifyItemPropertyChanged(this, aKey, oldValue, aValue); } return; } } WARN("Could not find nsIAbEDSCard mapper for property: " + aKey); }, commit: function EDSCard_commit() { for (let mapper in fixIterator(this._mappers)) { if (!mapper.commit()) { WARN("Could not commit mapper " + mapper + " - skipping..."); } } LOG("Flushing card!"); this.flush(this); LOG("Done flushing"); if (!this._exists) return this.commitNew(); LibEBookClient.modifyContact(this._EBookClient, this._eContact, null, modifyContactCbPtr, this._uriPtr); LOG("Commit complete!"); }, commitNew: function EDSCard_commitNew() { LOG("Within commitNew"); let newUid = new LibGLib.gchar.ptr(); let errPtr = new LibGLib.GError.ptr(); let result = LibEBookClient.addContactSync(this._EBookClient, this._eContact, newUid.address(), null, errPtr.address()); if (!result) { ERROR("Could not create new nsAbEDSCard - message was: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; this.dispose(); throw("Exiting without creating new nsAbEDSCard"); } this.dispose(); LOG("Disposed new EContact"); return; }, update: function EDSCard_update(aEContact) { LOG("Updating this card!"); let newEContact = LibEContact.duplicate(aEContact); var oldValues = {}; for (let mapper in fixIterator(this._mappers)) { let keys = mapper.keys; for (let i = 0; i < keys.length; i++) { let key = keys[i]; oldValues[key] = this.getProperty(key, ""); } mapper.EContact = newEContact; } this.flush(); LOG("Old values cached and flushed"); LOG("Unref'ing old EContact"); LibGLib.g_object_unref(this._eContact); this._eContact = newEContact; LOG("Notifying AB Manager that card was updated"); for (let key in oldValues) { LOG("Getting key: " + key); let newValue = this.getProperty(key, ""); if (oldValue != newValue) { MailServices.ab.notifyItemPropertyChanged(this, key, oldValue, newValue); } } LOG("Card update complete"); }, getPropertyAsAString: function EDSCard_getPropertyAsAString(aName) { return this.getProperty(aName, null); }, setPropertyAsAString: function EDSCard_setPropertyAsAString(aName, aValue) { this._props[aName] = aValue; }, getPropertyAsAUTF8String: function EDSCard_getPropertyAsAUTF8String(aName) { return this.getProperty(aName, null); }, setPropertyAsAUTF8String: function EDSCard_setPropertyAsAUTF8String(aName, aValue) { this._props[aName] = aValue; }, getPropertyAsUint32: function EDSCard_getPropertyAsUint32(aName) { return this.getProperty(aName, null); }, setPropertyAsUint32: function EDSCard_setPropertyAsUint32(aName, aValue) { this._props[aName] = aValue; }, getPropertyAsBool: function EDSCard_getPropertyAsBool(aName) { return this.getProperty(aName, null); }, setPropertyAsBool: function EDSCard_setPropertyAsBool(aName, aValue) { this._props[aName] = aValue; }, deleteProperty: function EDSCard_deleteProperty(aName) { }, generateName: function EDSCard_generateName(aGenerateFormat, aBundle) { return this.displayName; }, hasEmailAddress: function EDSCard_hasEmailAddress(aEmailAddress) { let emails = this.getEmailAddrs({}); for (let i = 0; i < emails.length; i++) { let email = emails[i]; if (email.address == aEmailAddress) return true; } return false; }, translateTo: function EDSCard_translateTo(aType) { return Cr.NS_ERROR_ILLEGAL_VALUE; }, generatePhoneticName: function EDSCard_generatePhoneticName(aLastNameFirst) { return ""; }, copy: function EDSCard_copy(aSrcCard) { }, equals: function EDSCard_equals(aCard) { return (aCard.localId == this.localId); }, _phoneField: function EDSCard__phoneField(aPropName, aEDSKey) { return aEDSKey; }, getPhoneNumbers: function EDSAb_getPhoneNumbers(aCount) { if (!this._phoneNumbers) this._fetchPhoneNumbers(); aCount.value = this._phoneNumbers.length; return this._phoneNumbers; }, setPhoneNumbers: function EDSAbCard_setPhoneNumbers(aCount, aAddrs) { let newAttrList = null; for (let i = 0; i < aCount; i++) { let EDSPhone = aAddrs[i]; let map = this._eVCardPropMap[EDSPhone.type]; if (!map) { WARN("Could not find a mapping for EDSPhone type: " + EDSPhone.type); continue; } let EVCAttr = LibEVCard.attributeNew("", LibEVCard.EVC_TEL); LibEVCard.attributeAddParamWithValue(EVCAttr, LibEVCard.attributeParamNew(LibEVCard.EVC_TYPE), map.type_1); if (map.type_2) { LibEVCard.attributeAddParamWithValue(EVCAttr, LibEVCard.attributeParamNew(LibEVCard.EVC_TYPE), map.type_2); } LibEVCard.attributeAddValue(EVCAttr, EDSPhone.number); newAttrList = LibGLib.g_list_append(newAttrList, EVCAttr); } // Get the old attribute list, clear out the top amount equal // to EMAIL_SLOTS, but leave the rest to concatenate onto the // new attribute list. let vCard = ctypes.cast(this._eContact, LibEVCard.EVCard.ptr); let oldAttrList = getAttributeListNamed(vCard, "TEL"); if (oldAttrList && !oldAttrList.isNull()) oldAttrList = chopAttrListTopBy(oldAttrList, this.PHONE_SLOTS); newAttrList = LibGLib.g_list_concat(newAttrList, oldAttrList); setAttributeListNamed(vCard, "TEL", newAttrList); freeAttrList(newAttrList); newAttrList = null; }, getIMAccounts: function EDSAbCard_getIMAccounts(aCount) { if (!this._IMAccounts) this._fetchIMAccounts(); aCount.value = this._IMAccounts.length; return this._IMAccounts; }, setIMAccounts: function EDSAbCard_setIMAccounts(aCount, aAccts) { let serviceAttrList = {}; let remainingSlots = this.IM_ACCOUNT_SLOTS; for (let i = 0; i < aCount; i++) { let EDSAcct = aAccts[i]; let vcardAttribute = LibEContact.vcardAttribute(LibEContact.getEnum(EDSAcct.type)).readString(); let EVCAttr = LibEVCard.attributeNew("", vcardAttribute); let param = LibEVCard.attributeParamNew(LibEVCard.EVC_TYPE); // Maybe at some point we should respect the "location", // of this attribute - but for now, we ignore it. LibEVCard.attributeAddParamWithValue(EVCAttr, param, LibEVCard.HOME); LibEVCard.attributeAddValue(EVCAttr, EDSAcct.username); if (!serviceAttrList[EDSAcct.type]) { serviceAttrList[EDSAcct.type] = LibGLib.g_list_append(null, EVCAttr); } else { serviceAttrList[EDSAcct.type] = LibGLib.g_list_append(serviceAttrList[EDSAcct.type], EVCAttr); } } for (let i = 0; i < this._eVCardIMServices.length; i++) { let svcType = this._eVCardIMServices[i]; let oldServiceAttrList = LibEContact.getAttributesRaw(this._eContact, LibEContact.getEnum(svcType)); let filledInSlots = Math.min(remainingSlots, LibGLib.g_list_length(oldServiceAttrList)); remainingSlots -= filledInSlots; if (oldServiceAttrList && !oldServiceAttrList.isNull()) oldServiceAttrList = chopAttrListTopBy(oldServiceAttrList, filledInSlots); if (!serviceAttrList[svcType]) { serviceAttrList[svcType] = LibGLib.g_list_concat(null, oldServiceAttrList); } else { serviceAttrList[svcType] = LibGLib.g_list_concat(serviceAttrList[svcType], oldServiceAttrList); } LibEContact.setAttributes(this._eContact, LibEContact.getEnum(svcType), serviceAttrList[svcType]); freeAttrList(serviceAttrList[svcType]); serviceAttrList[svcType] = null; } }, getEmailAddrs: function EDSAbCard_getEmailAddrs(aCount) { if (!this._emailAddrs) this._fetchEmailAddrs(); aCount.value = this._emailAddrs.length; return this._emailAddrs; }, setEmailAddrs: function EDSAbCard_setEmailAddrs(aCount, aAddrs) { let newAttrList = null; for (let i = 0; i < aCount; i++) { let EDSEmail = aAddrs[i]; let EVCAttr = LibEVCard.attributeNew("", LibEVCard.EVC_EMAIL); let param = LibEVCard.attributeParamNew(LibEVCard.EVC_TYPE); LibEVCard.attributeAddParamWithValue(EVCAttr, param, EDSEmail.type); LibEVCard.attributeAddValue(EVCAttr, EDSEmail.address); newAttrList = LibGLib.g_list_append(newAttrList, EVCAttr); } // Get the old attribute list, clear out the top amount equal // to EMAIL_SLOTS, but leave the rest to concatenate onto the // new attribute list. let oldAttrList = LibEContact.getAttributesRaw(this._eContact, LibEContact.getEnum("E_CONTACT_EMAIL")); if (oldAttrList && !oldAttrList.isNull()) oldAttrList = chopAttrListTopBy(oldAttrList, this.EMAIL_SLOTS); newAttrList = LibGLib.g_list_concat(newAttrList, oldAttrList); LibEContact.setAttributes(this._eContact, LibEContact.getEnum("E_CONTACT_EMAIL"), newAttrList); freeAttrList(newAttrList); newAttrList = null; }, _fetchPhoneNumbers: function EDSAb__fetchPhoneNumbers() { // Cast the EContact as a VCard this._phoneNumbers = []; let vCard = ctypes.cast(this._eContact, LibEVCard.EVCard.ptr); let attrs = LibEVCard.getAttributesNamed(vCard, "TEL"); for (let attr = attrs.next(); attr != null; attr = attrs.next()) { let phoneNumber = LibEVCard.getAttributeValue(attr); let phoneType = this._getPhoneType(attr); let EDSPhone = new nsAbEDSPhone(); EDSPhone.number = phoneNumber; EDSPhone.type = phoneType; this._phoneNumbers.unshift(EDSPhone); } }, _getPhoneType: function EDSAbCard__getPhoneType(aAttr) { for (let key in this._eVCardPropMap) { let map = this._eVCardPropMap[key]; if (LibEVCard.attributeHasType(aAttr, map.type_1) && (map.type_2 == null || LibEVCard.attributeHasType(aAttr, map.type_2))) return key; } return null; }, _fetchIMAccounts: function EDSAbCard__fetchIMAccounts() { this._IMAccounts = []; for (let i = 0; i < this._eVCardIMServices.length; i++) { // Retrieve and append any attributes for each service. let serviceEnum = this._eVCardIMServices[i]; let attrList = LibEContact .getAttributes(this._eContact, LibEContact.getEnum(serviceEnum)); for (let attr = attrList.next(); attr != null; attr = attrList.next()) { let EVCardAttribute = ctypes.cast(attr, LibEVCard.EVCardAttribute.ptr); let username = LibEVCard.getAttributeValue(EVCardAttribute); let EDSIMAccount = new nsAbEDSIMAccount(); EDSIMAccount.username = username; EDSIMAccount.type = serviceEnum; this._IMAccounts.unshift(EDSIMAccount); } } }, _fetchEmailAddrs: function EDSAbCard__fetchEmailAddrs() { this._emailAddrs = []; let attrs = LibEContact.getAttributes(this._eContact, LibEContact.getEnum("E_CONTACT_EMAIL")); for (let attr = attrs.next(); attr != null; attr = attrs.next()) { let EVCardAttribute = ctypes.cast(attr, LibEVCard.EVCardAttribute.ptr); let EDSEmail = new nsAbEDSEmailAddress(); let type = this._resolveEmailType(EVCardAttribute); let address = LibEVCard.getAttributeValue(EVCardAttribute); EDSEmail.address = address; EDSEmail.type = type; this._emailAddrs.push(EDSEmail); } }, _resolveEmailType: function EDSAbCard__resolveEmailType(aAttr) { for (let i = 0; i < this._emailAddressTypes.length; i++) { if (LibEVCard.attributeHasType(aAttr, this._emailAddressTypes[i])) return this._emailAddressTypes[i]; } WARN("Could not resolve email type for an nsAbEDSEmailAddress - " + "setting as OTHER."); return LibEVCard.OTHER; }, _makeExist: function EDSAbCard__makeExist() { this._uri = LibEContact.getStringConstProp(this._eContact, LibEContact.getEnum("E_CONTACT_UID")); this._uriPtr = LibGLib.gchar.array()(this._uri); if (gEContactLookup[this._uri]) WARN("gEContactLookup[" + this._uri + "] was overwritten in _makeExist"); gEContactLookup[this._uri] = this; this._exists = true; }, flush: function EDSAbCard_flush() { for (let mapper in fixIterator(this._mappers)) { LOG("Flushing mapper: " + mapper._EDSKey); mapper.flush(this); } this._IMAccounts = null; this._phoneNumbers = null; this._emailAddrs = null; }, dispose: function EDSAbCard_dispose() { this.flush(); var self = this; ["_EBookClient", "_eContact"].forEach(function(aKey) { let aPtr = self[aKey]; if (aPtr && !aPtr.isNull()) { LibGLib.g_object_unref(aPtr); } self[aKey] = null; }); } } function freeAttrList(aAttrList) { for (let l = aAttrList; !l.isNull(); l = l.contents.next) { let attr = ctypes.cast(l.contents.data, LibEVCard.EVCardAttribute.ptr); LibEVCard.attributeFree(attr); } LibGLib.g_list_free(aAttrList); } function getAttributeListNamed(aEVCard, aName) { let attrListOut = null; let attrListGen = LibEVCard.getAttributesNamed(aEVCard, aName); for(let attr = attrListGen.next(); attr != null; attr = attrListGen.next()) { attrListOut = LibGLib.g_list_append(attrListOut, LibEVCard.attributeCopy(attr)); } return attrListOut; } function setAttributeListNamed(aEVCard, aName, aAttrList) { LibEVCard.removeAttributes(aEVCard, null, aName); for (let i = aAttrList; !i.isNull(); i = i.contents.next) { let attr = ctypes.cast(i.contents.data, LibEVCard.EVCardAttribute.ptr); let copy = LibEVCard.attributeCopy(attr); LibEVCard.addAttribute(aEVCard, copy); } } function chopAttrListTopBy(aAttrList, aNum) { let l, l_next; let i; for (l = aAttrList, i = 0; !l.isNull() && i < aNum; l = l_next, i++) { LOG("Chopping an item"); l_next = l.contents.next; let oldAttr = ctypes.cast(l.contents.data, LibEVCard.EVCardAttribute.ptr); LibEVCard.attributeFree(oldAttr); l = LibGLib.g_list_delete_link(l, l); } return l; }