/* -*- 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 = [ "simpleStringMapper", "addressMapper", "dateMapper", "photoMapper", "simpleBooleanMapper", "EDSFieldMap", "nameMapper", "emailMapper" ]; 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://edsintegration/LibEContact.jsm"); Cu.import("resource://edsintegration/LibGLib.jsm"); Cu.import("resource://edsintegration/nsAbEDSCommon.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource:///modules/iteratorUtils.jsm"); var EDSFieldMap = { _map: { "FirstName": "E_CONTACT_GIVEN_NAME", "LastName": "E_CONTACT_FAMILY_NAME", "DisplayName": "E_CONTACT_FILE_AS", "NickName": "E_CONTACT_NICKNAME", "PrimaryEmail": null, "LastModifiedDate": null, "PopularityIndex": null, "AllowRemoteContent": null, "PhoneticFirstName": null, "PhoneticLastName": null, "SpouseName": "E_CONTACT_SPOUSE", "FamilyName": "E_CONTACT_FAMILY_NAME", "SecondEmail": null, "WebPage2": null, "WebPage1": "E_CONTACT_HOMEPAGE_URL", "HomePhone": "E_CONTACT_PHONE_HOME", "HomePhoneType": null, "WorkPhone": "E_CONTACT_PHONE_BUSINESS", "WorkPhoneType": null, "FaxNumber": "E_CONTACT_PHONE_HOME_FAX", "FaxNumberType": null, "PagerNumberType": null, "PagerNumber": "E_CONTACT_PHONE_PAGER", "CellularNumber": "E_CONTACT_PHONE_MOBILE", "CellularNumberType": null, "JobTitle": "E_CONTACT_TITLE", "Department": "E_CONTACT_ORG_UNIT", "Company": "E_CONTACT_ORG", "_AimScreenName": null, "Custom1": null, "Custom2": null, "Custom3": null, "Custom4": null, "Notes": "E_CONTACT_NOTE", "WorkFax": "E_CONTACT_PHONE_BUSINESS_FAX", "AssistantPhone": "E_CONTACT_PHONE_ASSISTANT", "HomePage": "E_CONTACT_HOMEPAGE_URL", "Blog": "E_CONTACT_BLOG_URL", "Calendar": "E_CONTACT_CALENDAR_URI", "FreeBusy": "E_CONTACT_FREEBUSY_URL", "VideoChat": "E_CONTACT_VIDEO_URL", "Spouse": "E_CONTACT_SPOUSE", "Profession": "E_CONTACT_ROLE", "Manager": "E_CONTACT_MANAGER", "Assistant": "E_CONTACT_ASSISTANT", "HomeAddress": "E_CONTACT_ADDRESS_HOME", "HomeAddress2": "E_CONTACT_ADDRESS_HOME", "HomeCity": "E_CONTACT_ADDRESS_HOME", "HomeState": "E_CONTACT_ADDRESS_HOME", "HomeZipCode": "E_CONTACT_ADDRESS_HOME", "HomeCountry": "E_CONTACT_ADDRESS_HOME", "HomePOBox": "E_CONTACT_ADDRESS_HOME", "WorkAddress": "E_CONTACT_ADDRESS_WORK", "WorkAddress2": "E_CONTACT_ADDRESS_WORK", "WorkCity": "E_CONTACT_ADDRESS_WORK", "WorkState": "E_CONTACT_ADDRESS_WORK", "WorkZipCode": "E_CONTACT_ADDRESS_WORK", "WorkCountry": "E_CONTACT_ADDRESS_WORK", "WorkPOBox": "E_CONTACT_ADDRESS_WORK", "WorkOffice": "E_CONTACT_OFFICE", "OtherAddress": "E_CONTACT_ADDRESS_OTHER", "OtherAddress2": "E_CONTACT_ADDRESS_OTHER", "OtherCity": "E_CONTACT_ADDRESS_OTHER", "OtherState": "E_CONTACT_ADDRESS_OTHER", "OtherZipCode": "E_CONTACT_ADDRESS_OTHER", "OtherCountry": "E_CONTACT_ADDRESS_OTHER", "OtherPOBox": "E_CONTACT_ADDRESS_OTHER", "BirthYear": "E_CONTACT_BIRTH_DATE", "BirthMonth": "E_CONTACT_BIRTH_DATE", "BirthDay": "E_CONTACT_BIRTH_DATE", "AnniversaryYear": "E_CONTACT_ANNIVERSARY", "AnniversaryMonth": "E_CONTACT_ANNIVERSARY", "AnniversaryDay": "E_CONTACT_ANNIVERSARY", "PreferMailFormat": "E_CONTACT_WANTS_HTML", "PreferMailFormatPopup": "E_CONTACT_WANTS_HTML", "Birthday": "E_CONTACT_BIRTH_DATE", "SpouseAnniversary": "E_CONTACT_ANNIVERSARY", "Age": "E_CONTACT_BIRTH_DATE", "PhotoName": "E_CONTACT_PHOTO", "PhotoURI": "E_CONTACT_PHOTO", "PhotoType": "E_CONTACT_PHOTO", }, // Returns an array... EDStoTB: function EDSFM_EDStoTB(aEDSField) { if (!this._reverseMap) this._generateReverse(); if (!this._reverseMap[aEDSField]) return null; return this._reverseMap[aEDSField]; }, TBtoEDS: function EDSFM_TBtoEDS(aTBField) { if (!this._map[aTBField]) return null; return this._map[aTBField]; }, EDSFieldIDtoTB: function EDSFM_EDSFieldIDtoTB(aEDSFieldId) { if (!this._fieldIdMap) this._generateFieldId(); if (!this._fieldIdMap[aEDSFieldId]) { return null; } return this._fieldIdMap[aEDSFieldId]; }, _generateReverse: function EDSFM__generateReverse() { this._reverseMap = {}; for (let tbField in this._map) { let EDSField = this._map[tbField]; if (!EDSField) continue; if (!this._reverseMap[EDSField]) this._reverseMap[EDSField] = []; this._reverseMap[EDSField].push(tbField); } }, _generateFieldId: function EDSFM__generateFieldId() { this._fieldIdMap = {}; for (let tbField in this._map) { let EDSField = this._map[tbField]; if (!EDSField) continue; let fieldId = LibEContact.getFieldName(LibEContact.getEnum(EDSField)); if (!this._fieldIdMap[fieldId]) this._fieldIdMap[fieldId] = []; this._fieldIdMap[fieldId].push(tbField); } }, } function emailMapper(aCard) { this._card = aCard; } emailMapper.prototype = { _keys: [ "PrimaryEmail", "SecondEmail", ], get keys() { return this._keys; }, read: function EM_read(aKey) { if (!this.readsKey(aKey)) return null; let addrs = this._card.getEmailAddrs({}); if (!addrs) return null; switch(aKey) { case "PrimaryEmail": if (addrs.length > 0) return addrs[0].address; break; case "SecondEmail": if (addrs.length > 1) return addrs[1].address; break; } return null; }, write: function EM_write(aKey, aValue) { return false; }, readsKey: function EM_readsKey(aKey) { return this.keys.indexOf(aKey) != -1; }, flush: function EM_flush(aCard) { this._card = aCard; }, commit: function EM_commit() { return true; }, cloneMap: function EM_cloneMap() { return {}; }, set EContact(aVal) { } } function simpleStringMapper(aEContact) { this._EContact = aEContact; this._cache = {}; this._propsFetched = []; } simpleStringMapper.prototype = { _keys: [ "DisplayName", "NickName", "SpouseName", "FamilyName", "WebPage1", "JobTitle", "Department", "Company", "Notes", "WorkFax", "WorkOffice", "AssistantPhone", "HomePage", "Blog", "Calendar", "FreeBusy", "VideoChat", "Spouse", "Profession", "Manager", "Assistant", ], get keys() { return this._keys; }, read: function SSM_read(aKey) { if (!this.readsKey(aKey)) return null; if (!this._cache[aKey]) { let propKey = EDSFieldMap.TBtoEDS(aKey); if (this._propsFetched.indexOf(aKey) != -1 || !propKey) return null; this._propsFetched.push(aKey); let result = LibEContact.getStringProp(this._EContact, LibEContact.getEnum(propKey)); this._cache[aKey] = result; } return this._cache[aKey]; }, write: function SSM_write(aKey, aValue) { if (!this.readsKey(aKey)) { WARN("Key not recognized: " + aKey); return false; } if (!aValue) aValue = ""; let propKey = EDSFieldMap.TBtoEDS(aKey); let propString = LibGLib.gchar.array()(aValue); LibEContact.setProp(this._EContact, LibEContact.getEnum(propKey), propString.address()); this._cache[aKey] = aValue; return true; }, readsKey: function SSM_readsKey(aKey) { return this.keys.indexOf(aKey) != -1; }, cloneMap: function SSM_cloneMap() { var result = {}; for (let i = 0; i < this._keys.length; i++) { let key = this._keys[i]; result[key] = this.read(key); } return result; }, flush: function SSM_flush(aCard) { this._cache = {}; this._propsFetched = []; }, commit: function SSM_commit() { return true; }, set EContact(aEContact) { this._EContact = aEContact; } } var multiValueMapper = { get keys() { return Object.keys(this._map); }, _load: function MVM_load() { let propPtr = LibEContact .getProp(this._EContact, LibEContact.getEnum(this._EDSKey)); if (propPtr == null || propPtr.isNull()) return null; // Cast to the appropriate type this._cache = ctypes.cast(propPtr, this._castTo); return true; }, read: function MVM_read(aKey) { if (!this._map[aKey]) return null; if (!this._cache && !this._load(aKey)) return null; var edsKey = this._map[aKey]; if (!this._cache.contents[edsKey]) { return null; } var strPtr = this._cache.contents[edsKey]; if (!this._doReadString) return strPtr; if (!strPtr || strPtr.isNull()) return null; return strPtr.readString(); }, write: function MVM_write(aKey, aValue) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, readsKey: function MVM_readsKey(aKey) { return this.keys.indexOf(aKey) != -1; }, cloneMap: function MVM_cloneMap() { var result = {}; for (let key in this._map) { result[key] = this.read(key); } return result; }, commit: function MVM_commit() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, set EContact(aEContact) { this._EContact = aEContact; } } function addressMapper(aEContact, aEDSKey, aAddress, aAddress2, aCity, aState, aZipCode, aCountry, aPOBox) { this._EContact = aEContact; this._EDSKey = aEDSKey; this._castTo = LibEContact.EContactAddress.ptr; this._map = {}; this._map[aAddress] = 'street'; this._map[aAddress2] = 'ext'; this._map[aCity] = 'locality'; this._map[aState] = 'region'; this._map[aZipCode] = 'code'; this._map[aCountry] = 'country'; this._map[aPOBox] = 'po'; this._cache = null; this._doReadString = true; this._preCommit = {}; } addressMapper.prototype = Object.create(multiValueMapper, { write: { value: function AM_write(aKey, aValue) { if (!this.readsKey(aKey)) return false; if (!aValue) aValue = ""; this._preCommit[aKey] = LibGLib.gchar.array()(aValue); return true; }}, commit: { value: function AM_commit() { this.flush(); this._cache = LibEContact.addressNew(); for (let key in this._map) { if (!this._preCommit[key]) continue; let edsField = this._map[key]; this._cache.contents[edsField] = this._preCommit[key] ? this._preCommit[key] : null; if (!this._cache.contents[edsField]) WARN("Ok, for some reason, this._preCommit[" + key + "] was null."); } LibEContact.setProp(this._EContact, LibEContact.getEnum(this._EDSKey), this._cache); // Free the address values we created so that // g_boxed_free doesn't try to do the job for // us when we flush. for (let key in this._map) { let edsField = this._map[key]; this._cache.contents[edsField] = null; } this._preCommit = {}; this.flush(); return true; }}, flush: { value: function AM_flush(aCard) { if (!this._cache) return; LibGLib.g_boxed_free(LibEContact.addressGetType(), this._cache); this._cache = null; }}, }); function dateMapper(aEContact, aEDSKey, aYear, aMonth, aDay) { this._EContact = aEContact; this._EDSKey = aEDSKey; this._castTo = LibEContact.EContactDate.ptr; this._map = {}; this._map[aYear] = 'year'; this._map[aMonth] = 'month'; this._map[aDay] = 'day'; this._cache = null; this._doReadString = false; this._preCommit = {}; } dateMapper.prototype = Object.create(multiValueMapper, { write: { value: function DM_write(aKey, aValue) { if (!this.readsKey(aKey)) return false; this._preCommit[aKey] = parseInt(aValue); return true; }}, commit: { value: function DM_commit() { let newDate = new LibEContact.EContactDate(); for (let key in this._map) { if (!this._preCommit[key]) continue; let edsField = this._map[key]; newDate[edsField] = this._preCommit[key] ? this._preCommit[key] : null; } LibEContact.setProp(this._EContact, LibEContact.getEnum(this._EDSKey), newDate.address()); this._preCommit = {}; this.flush(); return true; }}, flush: { value: function DM_flush(aCard) { if (!this._cache) return; LibEContact.dateFree(this._cache); this._cache = null; }} }); function nameMapper(aEContact, aEDSKey, aFamilyName, aGivenName) { this._EContact = aEContact; this._EDSKey = aEDSKey; this._castTo = LibEContact.EContactName.ptr; this._map = {}; this._map[aFamilyName] = 'family'; this._map[aGivenName] = 'given'; this._cache = null; this._doReadString = true; this._preCommit = {}; } nameMapper.prototype = Object.create(multiValueMapper, { write: { value: function NM_write(aKey, aValue) { if (!this.readsKey(aKey)) return false; if (!aValue) aValue = ""; this._preCommit[this._map[aKey]] = aValue; return true; }}, commit: { value: function NM_commit() { LibEContact.nameFree(this._cache); let nameString = this._preCommit['given'] + ' ' + this._preCommit['family']; this._cache = LibEContact.nameFromString(nameString); LibEContact.setProp(this._EContact, LibEContact.getEnum("E_CONTACT_NAME"), this._cache); LibEContact.setProp(this._EContact, LibEContact.getEnum("E_CONTACT_FULL_NAME"), LibGLib.gchar.array()(nameString)); this._preCommit = {}; return true; }}, flush: { value: function NM_flush(aCard) { if (!this._cache) return; LibEContact.nameFree(this._cache); this._cache = null; }} }); function photoMapper(aEContact) { this._EContact = aEContact; this._cache = null; this._EDSKey = "E_CONTACT_PHOTO"; this._newPhotoCache = null; } photoMapper.prototype = { get keys() { return ["PhotoName", "PhotoURI", "RawData", "RawDataLength", "MimeType", "PhotoType"]; }, _load: function PM__load() { let propPtr = LibEContact.getProp(this._EContact, LibEContact.getEnum(this._EDSKey)); if (propPtr.isNull()) { return false; } this._cache = ctypes.cast(propPtr, LibEContact.EContactPhoto.ptr); return true; }, read: function PR_read(aKey) { switch(aKey) { case "PhotoName": return ""; break; case "PhotoURI": return ""; break; case "PhotoType": if (this.read("RawDataLength") == 0) return "eds-generic"; else return "eds"; break; case "RawData": return this._getRawPhotoData(); break; case "RawDataLength": return this._getData("length"); break; case "MimeType": return this._getData("mime_type", true); } }, _getRawPhotoData: function PM__getRawPhotoData() { if (!this._cache && !this._load()) { return null; } var dataPtr = new LibGLib.guchar.ptr; dataPtr = this._cache.contents.data.data; var length = this._getData("length"); var result = ctypes.cast(dataPtr, LibGLib.guchar.array(length).ptr).contents; var data = String.fromCharCode.apply(null, result); return data; }, _getData: function PM__getData(aName, aConvertToString) { if (!this._cache && !this._load()) return null; let dataPtr = this._cache.contents.data[aName]; if (dataPtr == null || (aConvertToString && dataPtr.isNull())) return null; if (aConvertToString) dataPtr = dataPtr.readString(); return dataPtr; }, write: function PR_write(aKey, aValue) { // We can only write to RawData - all other writes are ignored. if (aKey != "RawData") return false; if (!this._cache && !this._load()) { // We're creating a photo fo the first time. LOG("Creating a new photo for EDS contact."); } else { LOG("Preparing to overwrite EDS contact photo - freeing..."); this.flush(null); } var newPhoto = LibEContact.EContactPhoto(); newPhoto.type = LibEContact.E_CONTACT_PHOTO_TYPE_INLINED; newPhoto.data.mime_type = null; this._newPhotoCache = newPhoto; if (aValue) { newPhoto.data.data = LibGLib.guchar.array()(aValue); newPhoto.data.length = aValue.length; } LibEContact.setProp(this._EContact, LibEContact.getEnum("E_CONTACT_PHOTO"), this._newPhotoCache.address()); LOG("New photo written."); return true; }, readsKey: function PR_readsKey(aKey) { return this.keys.indexOf(aKey) != -1; }, cloneMap: function PM_cloneMap() { var result = {}; var mime = this.read("MimeType"); var mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); var fileExt = ""; try { fileExt = mimeService.getPrimaryExtension(mimeType, fileExt); } catch(e) {} var data = this.read("RawData"); var dataLength = this.read("RawDataLength"); if (!data) { result["PhotoType"] = "generic"; return result; } var fileName = "edsContact" + "." + fileExt; var file = FileUtils.getFile("ProfD", ["Photos", fileName]); file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); var ostream = FileUtils.openSafeFileOutputStream(file); var istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); istream.setData(data, dataLength); NetUtil.asyncCopy(istream, ostream, function(status) {}); var photoURI = Services.io.newFileURI(file).spec; result["PhotoType"] = "file"; result["PhotoName"] = file.leafName; result["PhotoURI"] = photoURI; return result; }, flush: function PM_flush(aCard) { if (!this._cache) return; LibEContact.freePhoto(this._cache); this._cache = null; }, commit: function PM_commit() { return true; }, set EContact(aEContact) { this._EContact = aEContact; } } function simpleBooleanMapper(aEContact, aEDSKey, aKey, aTrue, aFalse) { this._EContact = aEContact; this._EDSKey = aEDSKey; this._true = aTrue; this._false = aFalse; this._key = aKey; this._cache = null; } simpleBooleanMapper.prototype = { get keys() { return [this._key]; }, _load: function SBM_load() { let propPtr = LibEContact.getProp(this._EContact, LibEContact.getEnum(this._EDSKey)); this._cache = propPtr; return true; }, read: function SBM_read(aKey) { if (aKey != this._key) return null; if (this._cache == null && !this._load()) { return null; } if (this._cache.isNull()) return this._false; else return this._true; }, write: function SBM_write(aKey, aValue) { let writeVal = (aValue == this._true) ? LibGLib.TRUE : LibGLib.FALSE; let writeValPtr = LibGLib.g_int_to_pointer(writeVal); LibEContact.setProp(this._EContact, LibEContact.getEnum(this._EDSKey), writeValPtr); return true; }, readsKey: function SBM_readsKey(aKey) { return this._key == aKey; }, cloneMap: function SBM_cloneMap() { var result = {}; result[this._key] = this.read(this._key); return result; }, flush: function SBM_flush(aCard) { this._cache = null; }, commit: function SBM_commit() { return true; }, set EContact(aEContact) { this._EContact = aEContact; } }