MailNews:Address Book Interface Redesign

From MozillaWiki
Jump to: navigation, search

With the interest of someone working on a sql backend for Address Book, various discussions have been held on irc regarding redesign of some of the interfaces (and hence classes in the address book).

The aim of the redesign would be to:

  • make it easier to add new address book types
  • support all the existing functionality we have already where possible
  • separate out the mailing list implementation from nsIAbCard and nsIAbDirectory
  • support as much functionality at implementation independent levels where possible
  • make it easier for groups/mailing lists/etc to be defined and controlled
  • could we make the structure to support recursive groups/mailing lists/etc?

This page is still WIP for even the initial design thoughts. Please use the Talk Page for comments

Current Structure

On Trunk as at 15th January 2008:

  • nsIAbManager
    • just about handles creation of new mork based address books. Sort-of handles LDAP books
    • Some utility functions
    • won't handle enable/removal of OSX or Outlook books.
  • nsIAbCard
    • Lots of attributes for each of the items on a card, can also represent mailing lists, but doesn't contain the list contains.
  • nsIAbDirectory
    • Represents an address book, is mostly implementation-independent but not quite.
    • Can also represent a mailing list, in which case most of the other properties are useless.

Thoughts that need clarification/decisions

  • Do we allow different Address Book types at different levels in a recursive structure?
    • Could be complicated with outlook books (one backend-book = multiple outlook books)

Revised nsIAbManager Interface

  • Handles addition/deletion of new address books (modifications of properties handled directly on nsIAbDirectory).
  • nsIAbListener implementation.
interface nsIAbManager : nsISupports
{
  /**
   * Returns an enumerator containing all the top-level directories
   * (non-recursive)
   */
  readonly attribute nsISimpleEnumerator directories;

  /**
   * Returns a directory object that is specific to the uuid given.
   *
   * @param  aUuid         The uuid of the directory to get.
   * @return               An object representing the directory of the given
   *                       uuid.
   * @exception NS_ERROR_FAILURE
   *                       The aUuid is invalid.
   */
  nsIAbDirectory getDirectory(in AUTF8String aUuid);
   
  /**
   * UNDER REVIEW
   * Creates a new directory of the specified type but does not register it
   * with the manager.
   *
   * @param  aType         The type of the directory, e.g. "sql", "ldap", etc.
   * @return               An object of the specific directory type whose
   *                       attributes can then be modified/set.
   */
  nsIAbDirectory createDirectory(in AUTF8String aType);

  /**
   * UNDER REVIEW
   * Registers a directory with the manager.
   *
   * @param  aDirectory    The directory to register.
   */
  void registerDirectory(in nsIAbDirectory aDirectory);

  /**
   * Deletes a directory and unregisters it.
   *
   * @param  aDirectory    The directory to delete.
   */
  void deleteDirectory(in nsIAbDirectory aDirectory);

  /**
   * UNDER REVIEW
   * Exports an address book, it will provide a dialog to the user for the
   * location to save the file to and will then save the address book to media.
   *
   * @param  aParentWin Parent Window for the file save dialog to use.
   * @param  aDirectory The directory to export.
   */
  void exportAddressBook(in nsIDOMWindow aParentWin, in nsIAbDirectory aDirectory);

  /**
   * Adds a nsIAbListener to receive notifications of address book updates
   * according to the specified notifyFlags.
   *
   * @param  aListener      The listener that is to receive updates.
   * @param  aNotifyFlags   A bitwise-or of abListenerNotifyFlagValue items
   *                        specifying which notifications to receive. See
   *                        nsIAbListener for possible values.
   */
  void addAddressBookListener(in nsIAbListener aListener,
                              in abListenerNotifyFlagValue aNotifyFlags);

  /**
   * Removes a nsIAbListener from receive notifications of address book
   * updates.
   *
   * @param  aListener     The listener that is to no longer receive updates.
   */
  void removeAddressBookListener(in nsIAbListener aListener);

  /**
   * UNDER REVIEW
   * Call to notify the registered listeners when a property on an item has
   * changed.
   *
   * @param  aItem         The items that has changed (e.g. an nsIAbDirectory)
   * @param  aProperty     The property that has changed (e.g. DirName)
   * @param  aOldValue     The old value of the property.
   * @param  aNewValue     The new value of the property.
   */
  void notifyItemPropertyChanged(in nsISupports aItem,
                                 in string aProperty,
                                 in wstring aOldValue,
                                 in wstring aNewValue);

  /**
   * UNDER REVIEW
   * Call to notify the registered listeners when a directory item is added.
   *
   * @param  aParentDirectory  The parent directory of the item that has been
   *                           added.
   * @param  aItem             The item that has been added.
   */
  void notifyDirectoryItemAdded(in nsIAbDirectory aParentDirectory,
                                in nsISupports aItem);

  /**
   * UNDER REVIEW
   * Call to notify the registered listeners when a directory item is removed.
   *
   * @param  aParentDirectory  The parent directory of the item that has been
   *                           removed.
   * @param  aItem             The item that has been removed.
   */
  void notifyDirectoryItemDeleted(in nsIAbDirectory aParentDirectory,
                                  in nsISupports aItem);
  
  /**
   * UNDER REVIEW
   * Call to notify the registered listeners when a directory is removed.
   *
   * @param  aParentDirectory  The parent directory of the directory that has
   *                           been removed.
   * @param  aDirectory        The directory that has been removed.
   */
  void notifyDirectoryDeleted(in nsIAbDirectory aParentDirectory,
                              in nsISupports aDirectory);
}

Notes/Thoughts:

  • Why does createCalendar have URI, why do they feel its necessary to set the URI in calCalendarManager?
  • Will the delete directory work - check what nsIAbDirectory implements.
  • Should we provide a nsISimpleEnumerator version of getDirectories, or is that overkill?
  • nsIAbDirFactory and its implementations won't be required.
  • Either here or somewhere similar I'd like to be able to register listeners to be notified of directory creation/registration/deleting.
  • Some (possibly more academic) thoughts: Superpig 18:13, 19 July 2008 (PDT)
    • It'd be nice to standardize the naming (change exportAddressBook and add/removeAddressBookListener to use 'directory' instead of 'address book').
    • Is it worth splitting the notify* methods out into a separate interface? It seems like it might be worth separating 'directory collection management' from 'directory event notification,' especially as the former is something directed at the client while the latter is directed at the directories. Could even drop them entirely in favour of a listener proxy object.
    • Seems odd having exportAddressBook here instead of on IAbDirectory.
    • What's with the asymmetry between create/register and delete? Either a directory is in a valid state when it's not registered, in which case there should probably be an unregister method, or it's not in a valid state, in which case create/register should be merged?

Proposed Structure For the Main Interfaces

  • Proposed Interface Inheritance Structure:
            nsIAbItem
                |
    |-------------------------|
nsIAbCard               nsIAbCollection
                              |
                    |--------------------|
                nsIAbGroup         nsIAbDirectory

nsIAbItem

  • Base address book item.
  • Contains functions/attributes generic to all items.
  • All items are derived from this one.
interface nsIAbItem : nsISupports
{
  /**
   * Unique identifier for this item. For items representing cards, mailing
   * lists, groups etc the uuid will remain the same throughout the life of the
   * item. If the item is moved, then the item will retain the same uuid where
   * possible. Copied items always will have a new uuid.
   *
   * @ exception NS_ERROR_ILLEGAL_VALUE  The supplied uuid is invalid.
   */
  attribute AUTF8String uuid;

  /**
   * These constants reflect the possible values of the
   * mail.addr_book.lastnamefirst preference. They are used in the generateName
   * function below
   */
  const unsigned long kGenerateDisplayName = 0;
  const unsigned long kGenerateLastFirstOrder = 1;
  const unsigned long kGenerateFirstLastOrder = 2;

  /** 
   * Generate a name from the item for display purposes.
   *
   * If this item is an nsIAbCard, then it will use the aGenerateFormat option
   * to determine the string to return.
   * If this item is not an nsIAbCard, then the aGenerateFormat option may be
   * ignored, and the displayName of the item returned.
   *
   * @param  aGenerateFormat The format to generate as per the kGenerate*
   *                         constants above.
   * @param  aBundle         An optional parameter that is a pointer to a string
   *                         bundle that holds:
   *           chrome://messenger/locale/addressbook/addressBook.properties
   *                         If this bundle is not supplied, then the function
   *                         will obtain the bundle itself. If cached by the
   *                         caller and supplied to this function, then
   *                         performance will be improved over many calls.
   * @return                 A string containing the generated name.
   */
  AString generateName(in long aGenerateFormat,
                       [optional] in nsIStringBundle aBundle);

  /**
   * Generate a formatted email address from the card, that can be used for
   * sending emails.
   *
   * @param  aExpandList     If this card is a list, and this parameter is set
   *                         to true, then the list will be expanded to include
   *                         the emails of the cards within the list.
   * @param  aGroupMailLists If this card (or the items within this card) is a
   *                         list, and this is set to true, then the list will
   *                         be expanded in the RFC 2822 group format
   *                         "displayname : email1 ; email2 ; etc".
   * @param  aHeaderParser   An optional parameter pointing to the
   *                         nsIMsgHeaderParser service. If this is not supplied
   *                         the function will obtain the service itself. If
   *                         cached by the called and supplied to this function,
   *                         then performance will be improved over many calls.
   * @return                 A string containing a comma-separated list of
   *                         formatted addresses.
   */
  AString generateFormattedEmail(in boolean aExpandList,
                                 in boolean aAsGroupMailLists,
                                 [optional] in nsIMsgHeaderParser aHeaderParser);

...
}

Notes/Thoughts:

  • Need a format (or at least examples) for the uuid.
  • Do we need a type attribute (e.g. LDAP/SQL etc) or is this better on nsIAbCollection?
  • Some sort of displayName attribute? -- Jcranmer 12:44, 17 February 2008 (PST)
    • Fixed --Standard8 10:16, 16 March 2008 (PDT)
      • Why here instead of nsIAbCard, and why a parameter? Superpig 18:21, 19 July 2008 (PDT)
  • To be clear, an "item" is any entity from which a name and one or more email addresses can be extracted, yes? Superpig 18:21, 19 July 2008 (PDT)

nsIAbCard

  • Represents a card.
  • No link to database. Modified card must be presented to directory in order for the database to be updated (this means we can easily enforce reducing the amount of database operations).
  • Generic attribute get/set.
  • Note: generateBase64EncodedXML, generateXMLPrintData and generateEscapedVCard have been replaced by translateTo()
interface nsIAbCard : nsIAbItem
{
  /**
   * A list of all the properties that this card has as an enumerator, whose
   * members are all nsIProperty objects.
   */
  readonly attribute nsISimpleEnumerator properties;

  /**
   * Returns a property for the given name.
   *
   * @param name             The case-sensitive name of the property to get.
   * @param defaultValue     The value to return if the property does not exist.
   * @exception NS_ERROR_NOT_AVAILABLE if the named property does not exist.
   * @exception NS_ERROR_CANNOT_CONVERT_DATA if the property cannot be converted
   *                                         to the desired type.
   */
  nsIVariant getProperty(in AUTF8String name, in nsIVariant defaultValue);
  /**
   * @{
   * Returns a property for the given name.
   *
   * These functions convert values in the same manner as the default
   * implementation of nsIVariant. Of particular note is that boolean variables
   * are converted to integers as in C/C++ (true is a non-zero value), so that
   * false will be converted to a string of "0" and not "false."
   *
   * These functions are marked [noscript] since xpconnect performs automatic
   * type conversion on nsIVariants such that they are not needed for scripts,
   * only for C++ callers.
   *
   * @param name             The case-sensitive name of the property to get.
   * @exception NS_ERROR_NOT_AVAILABLE if the named property does not exist.
   * @exception NS_ERROR_CANNOT_CONVERT_DATA if the property cannot be converted
   *                                         to the desired type.
   */
  [noscript] AString getPropertyAsAString(in string name);
  [noscript] AUTF8String getPropertyAsAUTF8String(in string name);
  [noscript] PRUint32 getPropertyAsUint32(in string name);
  [noscript] boolean getPropertyAsBool(in string name);
  /** @} */

  /**
   * @{
   * Assigns the given to value to the property of the given name.
   *
   * Should the property exist, its value will be overwritten. An
   * implementation may impose additional semantic constraints for certain
   * properties. However, such constraints might not be checked by this method.
   *
   * These functions convert values in the same manner as the default
   * implementation of nsIVariant.
   * 
   * @warning A value MUST be convertible to a string; if this convention is not
   * followed, consumers of cards may fail unpredictably or return incorrect
   * results.
   *
   * The non-variant functions are marked [noscript] since xpconnect uses
   * magic with nsIVariant such that the other functions are not needed,
   * although C++ does need them.
   *
   * @param name             The case-sensitive name of the property to set.
   * @param value            The new value of the property.
   */
  void setProperty(in AUTF8String name, in nsIVariant value);
  [noscript] void setPropertyAsAString(in string name, in AString value);
  [noscript] void setPropertyAsAUTF8String(in string name, in AUTF8String value);
  [noscript] void setPropertyAsUint32(in string name, in PRUint32 value);
  [noscript] void setPropertyAsBool(in string name, in boolean value);
  /** @} */

  /**
   * Deletes the property with the given name.
   *
   * Some properties may not be deleted. However, the implementation will not
   * check this constraint at this method. If such a property is deleted, an
   * error may be thrown when the card is modified at the database level.
   *
   * @param name             The case-sensitive name of the property to set.
   */
  void deleteProperty(in AUTF8String name);
 
  /**
   * @{
   * These properties are shorthand for getProperty and setProperty.
   */
  attribute AString firstName;
  attribute AString lastName;
  attribute AString displayName;
  attribute AString primaryEmail;
  /** @} */

  /**
   * Translates a card into a specific format.
   * The following types are supported:
   * - base64xml
   * - xml
   * - vcard
   *
   * @param  aType          The type of item to translate the card into.
   * @return                A string containing the translated card.
   * @exception NS_ERROR_ILLEGAL_VALUE if we do not recognize the type.
   */
  AUTF8String translateTo(in AUTF8String aType);

  /**
   * Translates a card from the specified format
   */
  void translateFrom(in AUTF8String aType, in AUTF8String aData);

  /** 
   * Generate a phonetic name from the card, using the firstName and lastName
   * values.
   *
   * @param  aLastNameFirst  Set to True to put the last name before the first.
   * @return                 A string containing the generated phonetic name.
   */
  AString generatePhoneticName(in boolean aLastNameFirst);

  // Some sort of copy and equals method to come
...
}

Notes/Thoughts:

  • If directories save their own attribute modifications, but cards don't, is this acceptable?
  • move generatePhoneticName up a level?
  • Is there a better exception that could be thrown by the set* methods if the card is read-only? "if the value is required" isn't terribly clear. Superpig 18:35, 19 July 2008 (PDT)
    • The current implementation doesn't throw on set (besides OOM). I may make debug builds do more stringent checking and assert some of the things, though.
  • Some sort of multi-valued attributes (cf. [1]) should probably be implemented.

nsIAbCollection

  • Represents a collection of nsIAbItem.
    • Currently unclear as to if we'll allow nsIAbDirectory items to be included in anything other than the top-level).
  • Contains methods for searching its nsIAbItem collection.
interface nsIAbCollection : nsIAbItem
{
  /**
   * Opens the properties dialog for this collection.
   */
  void openPropertiesDialog();

  /**
   * Returns true if this collection is read-only.
   */
  readonly attribute boolean readOnly;

  /**
   * Returns true if the collection requires specific protocol access over a
   * network connection.
   */
  readonly attribute boolean isRemote;

  /**
   * Returns true if the collection is remote and is accessed over a secure
   * protocol (as in LDAP over SSL).
   */
  readonly attribute boolean isSecure;

  /**
   * Returns an array of the child items (non-recursive).
   */
  void getChildItems(out PRUint32 aCount,
                     [array, size_is(count), retval] out nsIAbItem aChildItems);

  /**
   * Returns a new search instance, set up to be able to search the current
   * collection.
   *
   * @exception NS_ERROR_NOT_IMPLEMENTED  Search is not available for this
   *                                      collection.
   */
  readonly attribute nsIAbDirectorySearch searchInstance;

  /** 
   * Returns the first nsIAbCard that matches the specified property. This will
   * only return the first item found, unlike searching via the searchInstance.
   *
   * @param  aProperty      The property on the item to look up.
   * @param  aValue         The value to which to match on the property.
   * @param  aCaseSensitive Set to true for a case sensitive match.
   * @return                The card if it is found.
   * @exceptions NS_ERROR_NOT_FOUND
   *                        No matching cards have been found.
   */
  nsIAbCard cardForProperty(in ACString aProperty, in nsIVariant aValue,
                            in boolean aCaseSensitive);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens when deleting items with children?
   *
   * Adds an item to the collection.
   *
   * @param     aItem                     The item to add to the collection.
   * @return                              The item added. This may be different
   *                                      to aItem for implementation-specific
   *                                      items.
   * @exception NS_ERROR_????             The item could not be added to this
   *                                      collection.
   */
  void addItem(in nsIAbItem aItem);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens if one fails?
   *  What happens when deleting items with children?
   *
   * Adds multiple items to the collection.
   *
   * @param     aItems                    The items to add to the collection,
   *                                      these may be exchanged for new items
   *                                      to allow them to be replaced by
   *                                      implementation specific items.
   */
  void addItems(in PRUint32 aCount,
                [array, size_is(count), retval] out nsIAbItem aItems);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens when deleting items with children?
   *
   * Modifies an item in the collection.
   *
   * @param     aItem                     The item to modify.
   * @exception NS_ERROR_????             The modification was not successful.
   */
  void modifyItem(in nsIAbItem item);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens if one fails?
   *  What happens when deleting items with children?
   *
   * Modifies multiple items in the collection.
   *
   * @param     aItem                     The item to modify.
   * @exception NS_ERROR_????             The modification was not successful.
   */
  void modifyItems(in PRUint32 aCount,
                   [array, size_is(count), retval] out nsIAbItem aItems);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens when deleting items with children?
   *
   * Deletes an item in the collection.
   *
   * @param     aItem                     The item to delete.
   * @exception NS_ERROR_????             The deletion was not successful.
   */
  void deleteItem(in nsIAbItem item);

  /**
   * UNDER REVIEW:
   *  async io
   *  What happens if one fails?
   *  What happens when deleting items with children?
   *
   * Modifies multiple items in the collection. TBD: What happens if one fails?
   *
   * @param     aItem                     The item to delete.
   * @exception NS_ERROR_????             The deletion was not successful.
   */
  void deleteItems(in PRUint32 aCount,
                   [array, size_is(count), retval] out nsIAbItem aItems);
}

Notes/Thoughts:

  • Does this need any properties?
    • There are generic pref setters/getters that are wanted. nickName and description may be useful as well, although the former may be satisfied by a displayName on nsIAbItem and the latter on nsIAbGroup instead. -- Jcranmer 12:44, 17 February 2008 (PST)
      • I've just added the generic pref setters from the current nsIAbDirectory. I'm not keen on adding specific items for nickName and description because they are really mailing list specific --Standard8 15:29, 19 March 2008 (PDT)
  • Do we need an enumerator version of getChildItems?
  • How do we open property dialogs for new nsIAbCollections not created yet?
    • Create an instance via nsIAbManager, then call the function - I've still got to change the manager for this --Standard8 07:05, 25 January 2008 (PST)
  • lastModifiedDate?
  • What about email case-sensitivity?
  • Functions to delete items and modify cards (generic items?) are needed. Maybe copying as well. -- Jcranmer 12:44, 17 February 2008 (PST)
    • Added basics, but need to expand on these and think about async io especially. --Standard8 15:29, 19 March 2008 (PDT)
  • Again, I'd like an addListener() method of some kind to be notified when cards are added/changed.
    • Is nsIAbManager not good enough? --Standard8 15:29, 19 March 2008 (PDT)

nsIAbGroup

  • Interface for functions/attributes slightly more specific to groups of items.
  • Not sure what these are yet.
  • A Group could be a Mailing List, Saved Search Folder, Collection of Tags etc.
interface nsIAbGroup : nsIAbCollection
{
...
}

Notes/Thoughts:

nsIAbDirectory

  • Interface for functions/attributes slightly more specific to directories.
  • Not sure what these are yet.
interface nsIAbDirectory : nsICollection
{
  /**
   * @name  getXXXValue
   *
   * Helper functions to get different types of pref, but return a default
   * value if a pref value was not obtained.
   *
   * @param aName         The name of the pref within the branch dirPrefId to
   *                      get a value from.
   *
   * @param aDefaultValue The default value to return if getting the pref fails
   *                      or the pref is not present.
   *
   * @return              The value of the pref or the default value.
   *
   * @exception           NS_ERROR_NOT_INITIALIZED if the pref branch couldn't
   *                      be obtained (e.g. dirPrefId isn't set).
   */
  //@{
  long getIntValue(in string aName, in long aDefaultValue);
  boolean getBoolValue(in string aName, in boolean aDefaultValue);
  ACString getStringValue(in string aName, in ACString aDefaultValue);
  AUTF8String getLocalizedStringValue(in string aName, in AUTF8String aDefaultValue);
  //@}

  /**
   * @name  setXXXValue
   *
   * Helper functions to set different types of pref values.
   *
   * @param aName         The name of the pref within the branch dirPrefId to
   *                      get a value from.
   *
   * @param aValue        The value to set the pref to.
   *
   * @exception           NS_ERROR_NOT_INITIALIZED if the pref branch couldn't
   *                      be obtained (e.g. dirPrefId isn't set).
   */
  //@{
  void setIntValue(in string aName, in long aValue);
  void setBoolValue(in string aName, in boolean aValue);
  void setStringValue(in string aName, in ACString aValue);
  //@}
...
}

Notes/Thoughts: - Search during autocomplete - different way of doing pref? (Standard8 talk to IanN). - Need a delete directory function for tidy up? (or maybe in the factory)

nsIAbDirectorySearch

  • Provides an interface to be able to search collections.
  • Implementations may be specific to directory types.
interface nsIAbDirectorySearch : nsISupports
{
  /**
   * Starts the search.
   *
   * @param  aSearchParams  The parameters to use for the search.
   */
  void startSearch(in nsIAbBooleanExpression aSearchParams);

  /**
   * Stops/interrupts the search.
   */
  void stopSearch();
...
}

Notes/Thoughts: