/* -*- 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 = [ "nsAbEDSDirectory", "kDirScheme", ]; const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const kDirType = "moz-abedsdirectory"; const kDirScheme = kDirType + "://"; const kDirPrefId = "ldap_2.servers.eds"; const kFactoryContractID = "@mozilla.org/addressbook/directory-factory;1?name=" + kDirType const kDirContractID = "@mozilla.org/addressbook/directory;1?type=" + kDirType; const kDirClassID = "9bb88c4e-2498-4bc0-95b7-a80f3809ba04"; const kFactoryClassID = "d44d17c2-70a6-4c0c-8b5f-de4b7d478275"; const kAuthDialogURI = "chrome://edsintegration/content/authDialog.xul"; Cu.import("resource://gre/modules/ctypes.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/mailServices.js"); Cu.import("resource:///modules/iteratorUtils.jsm"); Cu.import("resource://edsintegration/LibGLib.jsm"); Cu.import("resource://edsintegration/LibEBookClient.jsm"); Cu.import("resource://edsintegration/LibEBookClientView.jsm"); Cu.import("resource://edsintegration/LibEBookQuery.jsm"); Cu.import("resource://edsintegration/LibEContact.jsm"); Cu.import("resource://edsintegration/LibESourceList.jsm"); Cu.import("resource://edsintegration/LibESource.jsm"); Cu.import("resource://edsintegration/LibEVCard.jsm"); Cu.import("resource://edsintegration/LibEList.jsm"); Cu.import("resource://edsintegration/nsAbEDSCard.jsm"); Cu.import("resource://edsintegration/nsAbEDSCommon.jsm"); Cu.import("resource://edsintegration/nsAbEDSMailingList.jsm"); Cu.import("resource://edsintegration/EDSFieldMappers.jsm"); Cu.import("resource://edsintegration/ReferenceService.jsm"); Cu.import("resource://edsintegration/LibEUri.jsm"); Cu.import("resource://edsintegration/LibEClient.jsm"); Cu.import("resource://edsintegration/LibGAsyncResult.jsm"); Cu.import("resource://edsintegration/LibECredentials.jsm"); Cu.import("resource://edsintegration/AuthHelper.jsm"); Cu.import("resource://edsintegration/ESourceProvider.jsm"); Cu.import("resource://edsintegration/nsAbEDSEmailAddress.jsm"); Cu.import("resource://edsintegration/nsAbEDSPhone.jsm"); Cu.import("resource://edsintegration/nsAbEDSIMAccount.jsm"); var gDeleteWarned = false; /* The nsAbEDSDirectory Factory, which implements nsIAbDirFactory. */ var nsAbEDSDirFactory = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIAbDirFactory]), getDirectories: function(aDirName, aURI, aPrefName) { let abUris = ESourceProvider.ESourceUids; let result = []; for (let i = 0; i < abUris.length; i++) { let abUri = kDirScheme + abUris[i]; try { let dir = MailServices.ab.getDirectory(abUri); result.push(dir); } catch(e) { ERROR("Could not properly create directory with uri: " + abUri + " - skipping"); ERROR("Error occurred at " + e.fileName + ":" + e.lineNumber, e); } } return CreateSimpleEnumerator(result); }, deleteDirectory: function(aDirectory) { // Currently unsupported. Alert the user and bail out. ERROR("Attempted to delete an EDS directory, which is currently" + " an unsupported action."); if (!gDeleteWarned) { Services.prompt.alert(null, _("CannotDeleteDirectoryTitle"), _("CannotDeleteDirectory")); gDeleteWarned = true; } throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, } /* And in order to get to the nsAbEDSDirectory Factory, I need to * register a...factory for it. */ var nsAbEDSDirFactoryFactory = { createInstance: function(aOuter, aIID) { if (aOuter != null) throw Cr.NS_ERROR_NO_AGGREGATION; if (!aIID.equals(Ci.nsIAbDirFactory)) throw Cr.NS_ERROR_NO_INTERFACE; return nsAbEDSDirFactory; } } function authRequestedCb(aEClient, aECredentials, aData) { LOG("An address book has requested authentication."); let uri = aData; let creds = aECredentials; let ab = gAbLookup[uri]; if (!ab) { ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); return; } if (ab._open) { return LibGLib.FALSE; } /* Ok, we need to authenticate. Let's get the important pieces: * 1) The auth-domain * 2) The component name * 3) The username * 4) The password * * 4 *might* be in the LoginManager, and if so, we'll try that. * Failing that, we'll prompt the user. */ let source = LibEClient.getSource(aEClient); let authMethod = LibESource.getProperty(source, "auth"); LOG("The authorization mechanism for this directory is: " + authMethod); // Maybe we don't even need to authenticate. if (!authMethod || authMethod == "none") { LOG("No need to authenticate for address book named " + this.dirName); ab._open = true; return LibGLib.FALSE; } let username = null; switch(authMethod) { case "ldap/simple-binddn": username = LibESource.getProperty(source, "binddn"); break; case "plain/password": username = LibESource.getProperty(source, "user"); if (!username) username = LibESource.getProperty(source, "username"); break; default: username = LibESource.getProperty(source, "email_addr"); break; } if (!username) username = ""; LOG("Retrieving any stored passwords for this address book..."); let password = null; let uri = LibEClient.getUri(aEClient); let strippedUri = stripUriParameters(uri); let authDomain = LibESource.getProperty(source, "auth-domain"); let componentName = authDomain ? authDomain : LibECredentials.E_CREDENTIALS_AUTH_DOMAIN_ADDRESSBOOK; // If these credentials haven't been used before, let's use the LoginManager // to try to find the password. let logins = Services.logins.findLogins({}, kExtensionChromeID, null, strippedUri); if (LibECredentials.peek(creds, LibECredentials.E_CREDENTIALS_KEY_PROMPT_FLAGS) == LibECredentials.E_CREDENTIALS_USED) { // We've used these credentials before, and since we're here, they were // no good. Let's forget these credentials. for (let i = 0; i < logins.length; i++) { let login = logins[i]; if (login.username == username) { LOG("Removing stored login for username " + username); Services.logins.removeLogin(login); break; } } } else { // Ok, let's see if the password for this username is in // the LoginManager for (let i = 0; i < logins.length; i++) { let login = logins[i]; if (login.username == username) { LOG("Found login for username " + username); password = login.password; break; } } } if (!password) { // Prompt for the username and password let title = _("AuthenticationTitle"); let reason = LibECredentials.peek(creds, LibECredentials.E_CREDENTIALS_KEY_PROMPT_REASON); var body; if (reason) body = _("AuthenticationBodyReason", [ab.dirName, reason]); else body = _("AuthenticationBodyBasic", [ab.dirName]); let checkboxBody = _("AuthenticationRememberPassword"); let userObj = {value: username}; let passwordObj = {value: ""}; let checkObj = {value: true}; let win = Services.wm.getMostRecentWindow("mail:addressbook"); if (!win) win = Services.wm.getMostRecentWindow("mail:3pane"); let answer = Services.prompt.promptUsernameAndPassword(win, title, body, userObj, passwordObj, checkboxBody, checkObj); if (!answer) { ab._cannotOpen = true; return LibGLib.FALSE; } username = userObj.value; password = passwordObj.value; if (checkObj.value && username && password) { let nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); // Try to store password for next time let loginInfo = new nsLoginInfo(kExtensionChromeID, null, strippedUri, username, password, "", ""); Services.logins.addLogin(loginInfo); } } LibECredentials.set(creds, LibECredentials.E_CREDENTIALS_KEY_USERNAME, username); LibECredentials.set(creds, LibECredentials.E_CREDENTIALS_KEY_PASSWORD, password); LibECredentials.set(creds, LibECredentials.E_CREDENTIALS_KEY_AUTH_METHOD, authMethod); LibECredentials.set(creds, LibECredentials.E_CREDENTIALS_KEY_AUTH_DOMAIN, authDomain); LibECredentials.set(creds, LibECredentials.E_CREDENTIALS_KEY_PROMPT_KEY, uri); return LibGLib.TRUE; } /* The "static" callback for when EContacts are changed in an * EBookView. * @param aEBookView the EBookView that contacts were changed in. * @param aGList the list of changed EContacts. * @param aData if cast to a gchar *, holds a URI that refers to * the nsAbEDSDirectory that contacts were changed in. */ function contactsChangedCb(aEBookView, aGList, aData) { LOG("Within the contactsChangedCb"); let uri = ctypes.cast(aData, LibGLib.gchar.ptr).readString(); LOG("Contacts updated for address book with uri: " + uri); let ab = gAbLookup[uri]; if (!ab) { ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); return; } // Go through each contact in the GList, find the // appropriate nsAbEDSCard, and update them. let glist = ctypes.cast(aGList, LibGLib.GList.ptr); for (let g = glist; !g.isNull(); g = g.contents.next) { let data = g.contents.data; let EContact = ctypes.cast(data, LibEContact.EContact.ptr); // Get the uid for this EContact, and use that // to look up the right nsAbEDSCard let uid = LibEContact.getStringConstProp(EContact, LibEContact.getEnum("E_CONTACT_UID")); // if we can't find the nsAbEDSCard, then that means // that the cards haven't been retrieved yet. That's // ok, just skip it. if (gEContactLookup[uid]) { LOG("Updating EContact with uid = " + uid); gEContactLookup[uid].update(EContact); LOG("Updated EContact with uid = " + uid); } else { WARN("Could not find an nsAbEDSContact with uid = " + uid + " - so will not update."); } } LOG("Exiting contactsChangedCb"); return; } function deleteContactCb(aGObject, aAsyncResult, aData) { let EBookClient = ctypes.cast(aGObject, LibEBookClient.EBookClient.ptr); if (!LibEBookClient.removeContactFinish(EBookClient, aAsyncResult, errPtr)) { ERROR("Could not delete contact - message was: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; } LOG("Successfully removed an EContact."); return; } var deleteContactCbPtr = LibGAsyncResult.GAsyncReadyCallback(deleteContactCb); /* The "static" callback for when EContacts are removed from an * EBookView. * @param aEBookView the EBookView that contacts were removed from. * @param aGList the list of removed EContacts * @param aData if cast to a gchar *, holds a URI that refers to * the nsAbEDSDirectory that contacts were added to. */ function contactsRemovedCb(aEBookView, aGList, aData) { LOG("Within the contactsRemovedCb"); let uri = ctypes.cast(aData, LibGLib.gchar.ptr).readString(); LOG("Contacts removed for address book with uri: " + uri); let ab = gAbLookup[uri]; if (!ab) { ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); return; } // Go through each contact in the GList, find the // appropriate nsAbEDSCards and nsAbEDSMailingLists, // and add them to our removal lists. let cardRemoval = []; let listRemoval = []; let glist = ctypes.cast(aGList, LibGLib.GList.ptr); for (let g = glist; !g.isNull(); g = g.contents.next) { let data = g.contents.data; let uid = ctypes.cast(data, LibGLib.gchar.ptr); if (uid.isNull()) continue; uid = uid.readString(); // if we can't find the nsAbEDSCard, then that means // that the cards haven't been retrieved yet. That's // ok, just skip it. if (gEContactLookup[uid]) { let edsContact = gEContactLookup[uid]; if (edsContact.isMailList) { LOG("Queueing an nsAbEDSMailingList for deletion."); listRemoval.push(edsContact); } else { LOG("Queueing an nsAbEDSCard for deletion."); cardRemoval.push(edsContact); } } else { WARN("Could not find an nsAbEDSContact or " + "nsAbEDSMailingList with uid = " + uid + " - so will not remove."); } } if (cardRemoval.length > 0) ab._disappearCards(cardRemoval); if (listRemoval.length > 0) ab._disappearDirectories(listRemoval); return; } /* The "static" callback for when EContacts are added to an * EBookView. * @param aEBookView the EBookView that contacts were added to. * @param aGList the list of added EContacts * @param aData if cast to a gchar *, holds a URI that refers to * the nsAbEDSDirectory that contacts were added to. */ function contactsAddedCb(aEBookClientView, aGSList, aData) { LOG("Within the contactsAddedCb"); let uri = ctypes.cast(aData, LibGLib.gchar.ptr).readString(); LOG("Contacts added for address book with uri: " + uri); let ab = gAbLookup[uri]; if (!ab) { ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); return; } // Go through each element of the GList, and cast as an // EContact. let gslist = ctypes.cast(aGSList, LibGLib.GSList.ptr); for (let g = gslist; !g.isNull(); g = g.contents.next) { let data = g.contents.data; let eContact = LibEContact.castPtr(data); let isMailList = LibEContact .getProp(eContact, LibEContact .getEnum("E_CONTACT_IS_LIST")); try { if (!isMailList.isNull()) ab._createMailList(eContact); else ab._createCard(eContact); } catch(e) { ERROR("Could not deal with EContact - " + e.fileName + ":" + e.lineNumber, e); } } return; } /* The "static" callback for when an EBookView is retrieved from * an EBookClient. * @param aEBook the EBook that we're retrieving the EBookView for. * @param aGError any errors that occurred while retrieving the EBookView. * @param aEBooKView the retrieved EBookView. * @param aData if cast to a gchar *, holds a URI that refers to the * nsAbEDSDirectory that we retrieved the EBookView for. */ function getBookViewCb(aGObject, aAsyncResult, aData) { LOG("Entered getBookViewCb"); let client = ctypes.cast(aGObject, LibEBookClient.EBookClient.ptr); let view = new LibEBookClientView.EBookClientView.ptr(); let errPtr = new LibGLib.GError.ptr(); if (!LibEBookClient.getViewFinish(client, aAsyncResult, view.address(), errPtr.address())) { ERROR("Problem retrieving EBookClientView"); if (!errPtr.isNull()) { ERROR("Message returned was: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; } else { ERROR("No message was returned."); } return; } let uri = ctypes.cast(aData, LibGLib.gchar.ptr); if (uri.isNull()) { ERROR("Could not extract URI within getBookViewCb"); return; } uri = uri.readString(); LOG("Got EBookView for address book with URI: " + uri); if (!gAbLookup[uri]) { ERROR("Could not find address book with URI: " + uri + " in the gAbLookup"); return; } let ab = gAbLookup[uri]; ab._EBookView = view; LOG("Setting EBookClientView callback pointers"); ab._refs['contactsAddedCbPtr'] = LibGLib.GCallback(contactsAddedCb); ab._refs['contactsChangedCbPtr'] = LibGLib.GCallback(contactsChangedCb); ab._refs['contactsRemovedCbPtr'] = LibGLib.GCallback(contactsRemovedCb); LOG("Connecting EBookClientView signals"); ab._refs.signal_connect(ab._EBookView, "objects-added", ab._refs['contactsAddedCbPtr'], ab._uri_ptr); ab._refs.signal_connect(ab._EBookView, "objects-modified", ab._refs['contactsChangedCbPtr'], ab._uri_ptr); ab._refs.signal_connect(ab._EBookView, "objects-removed", ab._refs['contactsRemovedCbPtr'], ab._uri_ptr); // Start the view let errPtr = new LibGLib.GError.ptr(); LibEBookClientView.start(ab._EBookView, errPtr.address()); if (!errPtr.isNull()) { ERROR("Could not start EBookClientView: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; return; } LOG("EBookClientView was successfully started!"); return; } var getBookViewCbPtr = LibGAsyncResult.GAsyncReadyCallback(getBookViewCb); /* The "static" callback for calls to e_book_get_supported_fields_async. * @param aEBook the EBook that supported fields are requested from * @param aGError any errors experienced while getting the supported fields * @param aEList the list of supported fields - these are "field names" - like * "pager", "address_work". See EContact documentation. * @param aData if cast to a gchar *, holds a URI that refers to the * nsAbEDSDirectory that requested supported fields. */ function getSupportedFieldsCb(aGObject, aAsyncResult, aData) { LOG("Supported fields for EBook retrieved - entered callback."); let client = ctypes.cast(aGObject, LibEClient.EClient.ptr); let propPtr = new LibGLib.gchar.ptr(); let errPtr = new LibGLib.GError.ptr(); if (!LibEClient.getBackendPropertyFinish(client, aAsyncResult, propPtr.address(), errPtr.address())) { ERROR("Problem retrieving supported fields"); if (!errPtr.isNull()) { ERROR("Message returned was: " + errPtr.contents.message.readString()); LibGLib.g_error_free(errPtr); errPtr = null; } else { ERROR("No message was returned."); } return; } let uri = ctypes.cast(aData, LibGLib.gchar.ptr).readString(); LOG("Got supported fields for EBook with URI: " + uri); let fieldString = propPtr.readString(); let fieldArray = fieldString.split(','); let ab = gAbLookup[uri]; if (!ab) ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); // Iterate through the EList, grab the field name, pop it into // an Array, and strap it to the nsAbEDSDirectory. let result = []; for(let i = 0; i < fieldArray.length; i++) { // Convert to an enum string let fieldId = LibEContact.fieldId(fieldArray[i]); result.push(fieldId); } ab._supportedFields = result; return; } var getSupportedFieldsCbPtr = LibGAsyncResult.GAsyncReadyCallback(getSupportedFieldsCb); /* The "static" callback for calls to e_book_open_async. * @param aEBook the EBook that was to be opened * @param aGError any errors experienced when opening this EBook * @param aData if cast to a gchar *, holds a URI that refers to the * nsAbEDSDirectory that tried to open the EBookClient. */ function openEClientCb(aGObject, aErrPtr, aData) { LOG("EClient open callback entered"); let uri = aData; let ab = gAbLookup[uri]; if (!ab) { ERROR("Tried to access an EDS address book through gAbLookup, " + "but no address book existed for URI: " + uri); return; } ab._openInProgress = false; if (aErrPtr && !aErrPtr.isNull()) { // Alert the user that there was a problem let errMsg = aErrPtr.contents.message.readString(); // We don't need to free aErrPtr - AuthHelper will take // care of that for us. ERROR("There was a problem opening this EClient: " + errMsg); return; } let client = ctypes.cast(aGObject, LibEClient.EClient.ptr); LOG("Opened EBook with URI: " + uri); let data = LibGLib.g_object_ref(client); ab._EClient = ctypes.cast(data, LibEClient.EClient.ptr);; ab._EBookClient = ctypes.cast(data, LibEBookClient.EBookClient.ptr); ab._EBookURI = LibEClient.getUri(ab._EClient); // Register the EBookClient with the lookup table if (!gEBookLookup[ab._EBookURI]) gEBookLookup[ab._EBookURI] = ab._EBookClient; // TODO: Connect auth-handler to "authorize" signal directly, // in case we're disconnected and need to reauthenticate. ab._open = true; ab._initSupportedFields(); if (ab._cardsRequested) { LOG("Cards have been requested - init'ing cards automatically."); ab._initCards(); } } /* nsAbEDSDirectory, implements nsIAbEDSDirectory, nsIAbDirectory, * nsIAbDirSearchListener and nsIAbDirectorySearch. */ function nsAbEDSDirectory() { this._uri = ""; this._dirName = ""; this._initialized = false; this._childCards = {}; this._childNodes = {}; this._didInitCards = false; this._open = false; this._cardsRequested = false; this._uri_ptr = null; this._book_view = null; this._EBookClient = null; this._EClient = null; this._eSource = null; this._addressLists = []; this._isQuery = false; this._query = ""; this._proxy = null; this._searchCache = []; this._EBookURI = null; this._supportedFields = null; this._authenticated = false; this._cannotOpen = false; } nsAbEDSDirectory.prototype = { classDescription: "Evolution Data Server Address Book Type", classID: Components.ID("{" + kDirClassID + "}"), contractID: kDirContractID, QueryInterface: XPCOMUtils.generateQI([Ci.nsIAbDirectory, Ci.nsIAbEDSDirectory, Ci.nsIAbDirSearchListener, Ci.nsIAbDirectorySearch]), /* If this nsAbEDSDirectory proxies to another nsAbEDSDirectory, * get the proxy. */ get proxy() { if (this._proxy) return this._proxy.proxy; return this; }, /* Return the EBook associated with this nsAbEDSDirectory. If we're * a proxy, get the EBook from the nsAbEDSDirectory that we proxy to. */ get eBook() { if (this._proxy) return this._proxy.eBook; return this._EBookClient; }, /* Return the ESource associated with this nsAbEDSDirectory. If we're * a proxy, get the ESource from the nsAbEDSDirectory that we proxy to. */ get eSource() { if (this._proxy) return this._proxy.eSource; return this._eSource; }, /* Return the UID for the ESource associated with this nsAbEDSDirectory. * If we're a proxy, get the UID for the ESource from the nsAbEDSDirectory * that we proxy to. */ get eSourceUid() { if (this._proxy) return this._proxy.eSourceUid; return this._eSourceUid; }, /* Called automatically by nsAbManager to boot up the nsIAbDirectory. * @param aURI the URI associated with this nsIAbDirectory */ init: function(aURI) { if (this._initialized) return; LOG("Init'ing nsAbEDSDirectory with URI: " + aURI); // Chop off any queries, and just take the root URI let queryPoint = aURI.indexOf("?"); if (queryPoint != -1) { this._isQuery = true; this._query = aURI.substr(queryPoint + 1); aURI = aURI.substr(0, queryPoint); } if (gAbLookup[aURI]) { if (!this._isQuery) { ERROR("Attempted to init a directory with pre-existing URI, " +"while not a query: " + aURI); throw("Exiting early."); } this._proxy = gAbLookup[aURI]; return; } else gAbLookup[aURI] = this; this._uri = aURI; this._uri_ptr = LibGLib.gchar.array()(this._uri); this._eSourceUid = aURI.substring(kDirScheme.length); // Get the ESource for this address book let ab_ptr = ESourceProvider.sourceListPtr; this._eSource = LibESourceList.peekSourceByUid(ab_ptr, this._eSourceUid); this._refs = ReferenceService.register(aURI); this._doOpen(); this._initialized = true; return; }, /* Close down the nsAbEDSDirectory. Stop any async operations. */ shutdown: function() { // Disconnect all signal handlers this._refs.dispose(); // Dispose of all child cards and mailing lists. if (this._didInitCards) { for (let card in fixIterator(this.childCards)) { if (!(card instanceof Ci.nsIAbEDSCard)) continue; card.dispose(); } for (let list in fixIterator(this.childNodes)) { if (!(list instanceof Ci.nsIAbEDSMailingList)) continue; list.dispose(); } } var self = this; // free all resources ["_EClient", "_eSource"].forEach(function(aKey) { let aPtr = self[aKey]; if (aPtr && !aPtr.isNull()) { LibGLib.g_object_unref(aPtr); } self[aKey] = null; }); }, /* Returns the supported fields for this nsAbEDSDirectory in the * form of EContactFields. See the EContact documentation for * further details. */ getSupportedFields: function(aCount) { if (!this._supportedFields) { aCount.value = 0; return []; } aCount.value = this._supportedFields.length; return this._supportedFields; }, /* The following is some boiler-plate nsIAbDirectory implementation * work. */ get propertiesChromeURI() { return kPropertiesChromeURI; }, get fileName() { return ""; }, get URI() { return this._uri; }, get position() { return ""; }, get childNodes() { this._cardsRequested = true; if (!this._didInitCards) this._initCards(); LOG("Returning " + Object.keys(this._childNodes).length + " mailing lists for " + this.URI); return CreateSimpleObjectEnumerator(this._childNodes); }, get childCards() { this._cardsRequested = true; if (this.isQuery) { LOG("I'm a query - starting search..."); this.startSearch(); return CreateSimpleEnumerator(this._searchCache); } if (!this._didInitCards) this._initCards(); return CreateSimpleObjectEnumerator(this._childCards); }, get isQuery() { return this._isQuery; }, get supportsMailingLists() { return true; }, get lastModifiedDate() { return 0; }, get readOnly() { return !this._open || LibEClient.isReadOnly(this._EClient); }, get isRemote() { return true; }, get isSecure() { return false; }, get dirPrefId() { return kDirPrefId; }, get uuid() { return this.dirPrefId + "." + this.dirName; }, set dirPrefId(val) { }, set lastModifiedDate(val) { }, get isMailList() { return false; }, set isMailList(val) { }, get dirName() { if (!this._dirName) this._dirName = LibESource.getName(this.eSource); return this._dirName; }, set dirName(aDirName) { LibESource.setName(this.eSource, aDirName); this._dirName = aDirName; }, get addressLists() { return []; }, set addressLists(val) { }, get listNickName() { return ""; }, set listNickName(val) { }, get description() { return ""; }, set description(val) { }, addMailList: function EDSAb_addMailList(aList) { // An nsIAbDirectory was passed in. We need to clone it as // an nsIAbEDSMailingList, and return the nsIAbEDSMailingList. LOG("Adding an EDS Mailing List"); let EContact = LibEContact.newEContact(); LibEContact.setProp(EContact, LibEContact.getEnum("E_CONTACT_IS_LIST"), LibGLib.g_int_to_pointer(LibGLib.TRUE)); let mlName = LibGLib.gchar.array()(aList.dirName); LibEContact.setProp(EContact, LibEContact.getEnum("E_CONTACT_FULL_NAME"), mlName); LibEContact.setProp(EContact, LibEContact.getEnum("E_CONTACT_FILE_AS"), mlName); let vcard = ctypes.cast(EContact, LibEVCard.EVCard.ptr); attachAddressListToVCard(vcard, aList.addressLists); let errorPtr = new LibGLib.GError.ptr(); let uidPtr = new LibGLib.gchar.ptr(); LibEBookClient.addContactSync(this._EBookClient, EContact, uidPtr.address(), null, errorPtr.address()); if (!uidPtr.isNull()) { var uid = uidPtr.readString(); LibGLib.g_free(uidPtr); uidPtr = null; } if (!errorPtr.isNull()) { ERROR("Could not add mailing list EContact to EBook: " + errorPtr.contents.message.readString()); LibGLib.g_free_error(errorPtr); errorPtr = null; return null; } if (!gEContactLookup[uid]) { ERROR("Could not return the newly created mailing list to" + " abMailListCommon.js - the mailing list was not" + " properly created in gEContactLookup with uid: " + uid); return null; } return gEContactLookup[uid]; }, deleteDirectory: function EDSAb_deleteDirectory(aDirectory) { if (!(aDirectory instanceof Ci.nsIAbEDSMailingList)) { ERROR("Could not delete a mailing list - was not an nsAbEDSMailingList"); return; } let uri = aDirectory.URI; if (!gMailListEContactLookup[uri]) { ERROR("Could not find an EContact with URI: " + uri + " within gMailListEContactLookup"); return; } LOG("Attempting to remove contact at gMailListEContactLookup[" + uri + "] = " + gMailListEContactLookup[uri]); let EContact = gMailListEContactLookup[uri]; LibEBookClient.removeContact(this._EBookClient, EContact, null, deleteContactCbPtr, this._uri_ptr); }, _disappearDirectories: function EDSAb__disappearDirectories(aDirectories) { for (let aDirectory in fixIterator(aDirectories)) { let uri = aDirectory.URI; if (!this._childNodes[uri]) { WARN("This nsAbEDSDirectory did not have a child directory registered" + " with URI: " + uri + ". Skipping."); return; } aDirectory.dispose(); // List is now un-useable. Remove from our collection. delete this._childNodes[uri]; MailServices.ab.notifyDirectoryItemDeleted(this, aDirectory); } }, hasCard: function EDSAb_hasCard(aCard) { let uri = aCard.localId; if (childCards[uri]) return true; return false; }, hasDirectory: function EDSAb_hasDirectory(aDirectory) { return false; }, addCard: function EDSAb_addCard(aCard) { let supportedFields = this.getSupportedFields({}); // Just clone from the nsIAbCard. var newCard = new nsAbEDSCard(this, this._EBookClient); for (let property in fixIterator(aCard.properties, Ci.nsIProperty)) { let EDSProp = EDSFieldMap.TBtoEDS(property.name); if (supportedFields.indexOf(EDSProp) != -1) newCard.setProperty(property.name, property.value); } if (aCard instanceof Ci.nsIAbEDSCard) { if (!cloneEDSCardFields(aCard, newCard)) { ERROR("Failed to clone EDS card"); return null; } } else { if (!cloneStandardCardFields(aCard, newCard)) { ERROR("Failed to clone standard TB card"); return null; } } return newCard; }, modifyCard: function EDSAb_modifyCard(aModifiedCard) { return aModifiedCard; }, deleteCards: function EDSAb_deleteCards(aCards) { for (let i = 0; i < aCards.length; i++) { let card = aCards.queryElementAt(i, Ci.nsIAbEDSCard); let uri = card.localId; // We need to access the EContact, so we'll grab the // Javascript implementation of this card from the lookup. if (!gEContactLookup[uri]) { WARN("Could not find an nsIAbEDSCard for URI " + uri + " within the gEContactLookup."); continue; } card = gEContactLookup[uri]; let EContact = card.EContact; LibEBookClient.removeContact(this._EBookClient, EContact, null, deleteContactCbPtr, this._uri_ptr); } }, _disappearCards: function EDSAb__disappearCards(aCards) { LOG("Deleting " + aCards.length + " card(s)"); for (let card in fixIterator(aCards)) { let uri = card.localId; if (!this._childCards[uri]) { WARN("This nsAbEDSDirectory did not have a child card registered" + " with URI: " + uri + ". Skipping."); continue; } MailServices.ab.notifyDirectoryItemDeleted(this, card); card.dispose(); // Card is now un-useable. Remove from our collection. delete this._childCards[uri]; } LOG("Done deleting cards."); }, dropCard: function EDSAb_dropCard(aCard, aNeedToCopyCard) { let newCard = this.addCard(aCard); newCard.commit(); LOG("Drag 'n drop card committed!"); }, useForAutoComplete: function EDSAb_useForAutoComplete(aIdentityKey) { return false; }, editMailListToDatabase: function EDSAb_editMailListToDatabase(aListCard) { }, copyMailList: function EDSAb_copyMailList(aSrcList) { }, createNewDirectory: function EDSAb_createNewDirectory(aDirName, aURI, aType, aPrefName) { return ""; }, createDirectoryByURI: function EDSAb_createDirectoryByURI(aDisplayName, aURI) { }, getIntValue: function EDSAb_getIntValue(aName, aDefaultValue){ return aDefaultValue; }, getBoolValue: function EDSAb_getBoolValue(aName, aDefaultValue) { return aDefaultValue; }, getStringValue: function EDSAb_getStringValue(aName, aDefaultValue) { return aDefaultValue; }, getLocalizedStringValue: function EDSAb_getLocalizedStringValue(aName, aDefaultValue) { return aDefaultValue; }, setIntValue: function EDSAb_setIntValue(aName, aValue){ }, setBoolValue: function EDSAb_setBoolValue(aName, aValue) { }, setStringValue: function EDSAb_setStringValue(aName, aValue) { }, setLocalizedStringValue: function EDSAb_setLocalizedStringValue(aName, aValue) { }, cardForEmailAddress: function EDSAb_cardForEmailAddress(aEmailAddress) { var cards = this.childCards; LOG("Checking for card with email address: " + aEmailAddress); while(cards.hasMoreElements()) { let card = cards.getNext(); if (card.hasEmailAddress(aEmailAddress)) return card; } return null; }, getCardFromProperty: function EDSAb_getCardFromProperty(aProperty, aValue, aCaseSensitive) { if (!aCaseSensitive) aValue = aValue.toLowerCase(); for each (let card in fixIterator(this.childCards)) { let property = card.getProperty(aProperty) if (!aCaseSensitive) property = property.toLowerCase(); if (property == aValue) return card; } return null; }, getCardsFromProperty: function EDSAb_getCardsFromProperty(aProperty, aValue, aCaseSensitive) { let result = [] if (!aCaseSensitive) aValue = aValue.toLowerCase(); for each (let card in fixIterator(this.childCards)) { let property = card.getProperty(aProperty) if (!aCaseSensitive) property = property.toLowerCase(); if (property == aValue) result.push(card); } return CreateSimpleEnumerator(result); }, generateName: function EDSAb_generateName(aGenerateFormat, aBundle) { return this.dirName; }, useForAutocomplete: function EDSAb_useForAutocomplete(aIdentity) { return true; }, startSearch: function EDSAb_startSearch() { if (!this.isQuery) return Cr.NS_ERROR_FAILURE; this._performingSearch = true; let args = Cc["@mozilla.org/addressbook/directory/query-arguments;1"] .createInstance(Ci.nsIAbDirectoryQueryArguments); let expression = MailServices.ab.convertQueryStringToExpression(this._query); args.expression = expression; args.querySubDirectories = false; let queryProxy = Cc["@mozilla.org/addressbook/directory-query/proxy;1"] .createInstance(Ci.nsIAbDirectoryQueryProxy); queryProxy.initiate(); LOG("This proxy is: " + this.proxy); let context = queryProxy.doQuery(this.proxy, args, this, -1, 0); return; }, stopSearch: function EDSAb_stopSearch() { }, onSearchFinished: function EDSAb_onSearchFinished(aResult, aErrorMsg) { }, onSearchFoundCard: function EDSAb_onSearchFoundCard(aCard) { this._searchCache.push(aCard); }, /* Attempt to retrieve cards for this EBookClient. */ _initCards: function EDSAb__initCards() { LOG("Requesting an initCards"); if (!this._open && !this._openInProgress) { this._doOpen(); return; } if (this._didInitCards || !this._open) return; LOG("Init'ing cards with EBookClient: " + this._EBookClient); // Construct the EBookQuery LOG("Constructing EBookQuery"); var query = LibEBookQuery.anyFieldContains(""); var queryString = LibEBookQuery.queryToString(query); LibEBookClient.getView(this._EBookClient, queryString, null, getBookViewCbPtr, this._uri_ptr); // Grab the EBookView for this EBook LOG("Requesting EBookClientView..."); this._didInitCards = true; }, _createCard: function EDSAb__createCard(aEContact) { let uid = LibEContact.getStringConstProp(aEContact, LibEContact.getEnum("E_CONTACT_UID")); let card = new nsAbEDSCard(this, this._EBookClient, aEContact); this._childCards[uid] = card; MailServices.ab.notifyDirectoryItemAdded(this, card); }, _createMailList: function EDSAb__createMailList(aEContact) { LOG("Within _createMailList"); let data = LibGLib.g_object_ref(aEContact); let mailingListPtr = ctypes.cast(data, LibEContact.EContact.ptr); gMailListCount++; let uri = this._uri + "/MailList-" + gMailListCount; LOG("Setting gMailListEContactLookup[" + uri + "] to " + mailingListPtr); gMailListEContactLookup[uri] = mailingListPtr; gMailListEBookLookup[uri] = this._EBookClient; let mailingList = MailServices.ab.getDirectory(uri); this._childNodes[uri] = mailingList; MailServices.ab.notifyDirectoryItemAdded(this, mailingList); }, _initSupportedFields: function EDSAb__initSupportedFields() { if (this._didInitSupportedFields || !this._open) return; LOG("Attempting to init supported fields"); LOG("This EClient is: " + this._EClient); LibEClient.getBackendProperty(this._EClient, "supported-fields", null, getSupportedFieldsCbPtr, this._uri_ptr); this._didInitSupportedFields = true; LOG("Supported fields init'd."); }, _doOpen: function EDSAb__doOpen() { if (this._openInProgress || this._cannotOpen) return; AuthHelper.openAndAuthESource(this._eSource, AuthHelper.ADDRESSBOOK, false, null, authRequestedCb, this._uri, openEClientCb, this._uri, this._uri); this._openInProgress = true; }, } function stripUriParameters(aURI) { let euri = LibEUri.newEUri(aURI); let result = LibEUri.EUriToString(euri, LibGLib.FALSE); LibEUri.EUriFree(euri); return result; } function cloneEDSCardFields(aCard, aClone) { let addrs = aCard.getEmailAddrs({}); aClone.setEmailAddrs(addrs.length, addrs); let phones = aCard.getPhoneNumbers({}); aClone.setPhoneNumbers(phones.length, phones); let IMAccounts = aCard.getIMAccounts({}); aClone.setIMAccounts(IMAccounts.length, IMAccounts); return true; } function cloneStandardCardFields(aCard, aClone) { // Does this card have any of the "Custom" fields set? // EContacts don't really have a spot for those. If the // contact has one of the Custom's set, get the user to // confirm the add, and tell them that the Custom data // will be (ugh) appended to the Notes field. let customs = []; // Oof - the magic number "4" is for the number of Custom fields // that TB cards have. for (let i = 1; i <= 4; i++) { let prop = aCard.getProperty("Custom" + i, ""); if (prop) customs.push(prop); } if (customs.length > 0) { let contactName = aCard.displayName; if (!Services.prompt.confirm(null, _("CannotCopyCustomFromTBTitle"), _("CannotCopyCustomFromTB", [contactName]))) { WARN("User cancelled out of copying a contact"); return false; } let customsString = "\n\n" + customs.join("\n"); let currentNotes = aClone.getProperty("Notes", ""); aClone.setProperty("Notes", currentNotes + customsString); } // Copy the PrimaryEmail and SecondEmail fields let addrs = []; let primaryEmail = aCard.getProperty("PrimaryEmail", null); let secondEmail = aCard.getProperty("SecondEmail", null); [primaryEmail, secondEmail].forEach(function(aEmail) { if (!aEmail) return; let newEmail = new nsAbEDSEmailAddress(); newEmail.address = aEmail; newEmail.type = LibEVCard.OTHER; addrs.push(newEmail); }); aClone.setEmailAddrs(addrs.length, addrs); // Copy any phone numbers let phones = []; ["HomePhone", "WorkPhone", "FaxNumber", "PagerNumber", "CellularNumber"].forEach(function(aPhoneKey) { let phone = aCard.getProperty(aPhoneKey, null); if (!phone) return; let newPhone = new nsAbEDSPhone(); newPhone.number = phone; newPhone.type = EDSFieldMap.TBtoEDS(aPhoneKey); phones.push(newPhone); }); aClone.setPhoneNumbers(phones.length, phones); // Now copy the AIM username, if one exists. let IMAccounts = []; let aimScreenname = aCard.getProperty("_AimScreenName", null); if (aimScreenname) { let IMAccount = new nsAbEDSIMAccount(); IMAccount.username = aimScreenname; IMAccount.type = "E_CONTACT_IM_AIM"; IMAccounts.push(IMAccount); } aClone.setIMAccounts(IMAccounts.length, IMAccounts); aClone.setProperty("RawData", aCard.getProperty("RawData", null)); return true; } // TODO: Move this crap into a single init function var nsAbEDSDirectoryFactory = { createInstance: function(aOuter, aIID) { if (aOuter != null) throw Cr.NS_ERROR_NO_AGGREGATION; if (!aIID.equals(Ci.nsIAbDirectory)) throw Cr.NS_ERROR_NO_INTERFACE; return new nsAbEDSTransformer(); } } /* So nsAbEDSTransformer is my way of avoiding having a lot of * if (this.isMailList) clauses within my nsAbEDSDirectory. The * way TB defines mailing lists is really quite unfortunate, and * can result in some pretty horrific code (see the OSX address * book implementation, for example). The transformer is what * is instantiated by the nsIAbManager with getDirectory. On * the init call, the URI is analyzed to determine whether or * not a mailing list is being requested. If we're requesting * a mailing list, the nsAbEDSTransformer "transforms" itself * to become an nsAbEDSMailingList. Otherwise, it "transforms" * itself to become an nsAbEDSDirectory. */ function nsAbEDSTransformer() { this._uri = ""; this._dirName = ""; this._initialized = false; this._childCards = {}; this._childNodes = {}; this._didInitCards = false; this._open = false; this._cardsRequested = false; this._uri_ptr = null; this._book_view = null; this._EBookClient = null; this._EClient = null; this._eSource = null; this._addressLists = []; this._isQuery = false; this._query = ""; this._proxy = null; this._searchCache = []; this._EBookURI = null; this._supportedFields = null; this._authenticated = false; // Used by nsAbEDSMailingList this._EDSUid = null; } nsAbEDSTransformer.prototype = { init: function (aURI) { var self = this; var prototype; // Determine if we're creating a mailing list // or a regular directory. if (aURI.indexOf("MailList-") != -1) { prototype = nsAbEDSMailingList.prototype; } else { prototype = nsAbEDSDirectory.prototype; } // Transform ourselves into that object. let objKeys = Object.keys(prototype); for (let i = 0; i < objKeys.length; i++) { let desc = Object.getOwnPropertyDescriptor(prototype, objKeys[i]); Object.defineProperty(self, objKeys[i], desc); } // Run the transformed init call on ourselves this.init(aURI); }, } Components.manager.QueryInterface(Ci.nsIComponentRegistrar) .registerFactory(Components.ID("{" + kDirClassID + "}"), "EDS Address Book Directory Factory", kDirContractID, nsAbEDSDirectoryFactory); Components.manager.QueryInterface(Ci.nsIComponentRegistrar) .registerFactory(Components.ID("{" + kFactoryClassID + "}"), "EDS Address Book Factory Factory", kFactoryContractID, nsAbEDSDirFactoryFactory);