Labs/Sprints/People: Difference between revisions

< Labs‎ | Sprints
 
(16 intermediate revisions by 2 users not shown)
Line 27: Line 27:


== Structure ==
== Structure ==
There will be a single XPCOM component implemented in JavaScript, and an IDL file to go along with it.  The component can then be dropped into any extensions that need to use the interface, and eventually uplifted into Firefox.
The backend store is implemented as a JS module.


== API ==
== API ==
The component will provide a service which can be instantiated like so:
First you'll need to import the module:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
let people = Components.utils.import("resource://mozilla/labs/people.js");
Components.utils.import("resource://people/modules/people.js");
</pre>
</pre>


This service will support these API calls:
Once imported you'll have access to these API calls:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
guid = people.add(object)
failed_person:object = People.add(person:object)
bool = people.remove(guid)
failed_person:object = People.update(person:object)
bool = people.update(object)
success:bool = People.changeGUID(oldGUID:string, newGUID:string)
removed:int People.remove(attrs:object)
[person:object, ...] = People.find(attrs:object)
</pre>
</pre>


The same calls also support multiple items passed in as arrays:
Some of the calls also support multiple items passed in as arrays:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
[guid, guid2] = people.add([object, object2])
[failed:object, ...] = People.add([person:object, ...])
[bool, bool2] = people.remove([guid, guid2])
[failed:object, ...] = People.update([person:object, ...])
[bool, bool2] = people.update([object, object])
[removed:int, ...] = People.remove([attrs:object, ...])
</pre>
</pre>


And there is also a find method:
Currently, the store uses transactions internally to add each person (because data is split up over multiple tables), so there is no public API for dealing with database transactions.  In the future, we may add this functionality.


<pre class="brush:js;toolbar:false;">
The object to pass in to <tt>add</tt> and <tt>update</tt> and returned by <tt>find</tt> look like the sample below. Currently, by default, we're storing a single [http://portablecontacts.net/draft-spec.html#schema Portable Contacts object] within the wrapper object.  Note that the schema for the 'default' document is contained in the <tt>default</tt> property of <tt>documentSchemas</tt>.  Be sure to verify the schema string, as the People store will not enforce adherence to Portable Contacts or any other schema.
[object, ...] =  people.find(object)
 
<pre>
// People object
{
  // guid identifies this person, schema describes the wrapper object
  guid: "af24d-fe488-ab748-b947f",
  schema: "http://labs.mozilla.com/schemas/people/1",
 
  // we pull out some fields for convenience.
  // we only index fields we pull out.
  displayName: "the foobinator",
  givenName: "foo",
  familyName: "bar",
 
  documents: {
    default: {
      displayName: "the foobinator",
      name{
        givenName: "foo",
        familyName: "bar"
      },
      emails: [
        {value: "foo@gmail.com", type: "home"},
        {value: "foo@yahoo.com", type: "work"},
        {value: "bar@yahoo.com", type: "other"}
      ]
    }
  },
  documentSchemas: {
    default: "http://portablecontacts.net/draft-spec.html"
  }
}
</pre>
</pre>


In all cases the structure of the object will only be loosely defined, but could look something like this:
=== Notifications ===
Notifications will be sent for modifications to the people storage such as adding a new person. The subject of the notification is usually the GUID of the item. The following list shows the function and the notification topic sent with the subject data.


<pre class="brush:js;toolbar:false;">
<pre>
{"firstname": "John",
add: "people-add" (guid)
"lastname": "Doe",
update: "people-update" (guid)
"identities": [
remove: "people-before-remove" (guid)
  {"type": "gmail",
remove: "people-remove" (guid)
    "firstname": "Johnnie",
changeGUID: "people-guid-change" ({ from: from, to: to })
    "email": "jdoe@gmail.com", ...}
]
}
</pre>
</pre>
The notification for remove will notify first before removing the item and afterwards, so listeners of people-before-remove can still get the item before it disappears.
If there are multiple items removed from a single remove call, each item will have its own notification.


== Performance ==
== Performance ==

Latest revision as of 08:57, 8 August 2009

Overview

Drivers: Dan Mills (thunder), Aza Razkin (aza)
Get involved: by hopping onto #labs on irc.mozilla.org or clicking on Discussion and leaving your comments

Description
Create a component that stores people (contacts) data in nearly schema-less form, and provides a simple API to add, remove, replace, and find people.


Goals / Use Cases

  • ability to store arbitrary contact data in a loose JSON format
  • ability to combine contact information from multiple sources while keeping it separate at the same time ("meta-contacts")
  • fast searching of specific well-known fields
  • easy-to-use API, fully scriptable
  • should be kept to as few files as possible, and be easy to embed in multiple extensions (e.g. Jetpack and Weave)
  • needs to deal gracefully with version changes and schema upgrades

Non Goals

  • this sprint will not implement any UI or visible interfaces for users

Download

not yet

History

Both Jetpack and Weave have discussed possible features around people, and require Firefox to keep an easy to use database. For example, see JEP 15 for a proposed Jetpack API. It makes sense for multiple projects to share a database, and keeping it separate will make it easier to potentially uplift it to Firefox as well.

Proposed Design

Structure

The backend store is implemented as a JS module.

API

First you'll need to import the module:

Components.utils.import("resource://people/modules/people.js");

Once imported you'll have access to these API calls:

failed_person:object = People.add(person:object)
failed_person:object = People.update(person:object)
success:bool = People.changeGUID(oldGUID:string, newGUID:string)
removed:int People.remove(attrs:object)
[person:object, ...] =  People.find(attrs:object)

Some of the calls also support multiple items passed in as arrays:

[failed:object, ...] = People.add([person:object, ...])
[failed:object, ...] = People.update([person:object, ...])
[removed:int, ...] = People.remove([attrs:object, ...])

Currently, the store uses transactions internally to add each person (because data is split up over multiple tables), so there is no public API for dealing with database transactions. In the future, we may add this functionality.

The object to pass in to add and update and returned by find look like the sample below. Currently, by default, we're storing a single Portable Contacts object within the wrapper object. Note that the schema for the 'default' document is contained in the default property of documentSchemas. Be sure to verify the schema string, as the People store will not enforce adherence to Portable Contacts or any other schema.

// People object
{
  // guid identifies this person, schema describes the wrapper object
  guid: "af24d-fe488-ab748-b947f",
  schema: "http://labs.mozilla.com/schemas/people/1",

  // we pull out some fields for convenience.
  // we only index fields we pull out.
  displayName: "the foobinator",
  givenName: "foo",
  familyName: "bar",

  documents: {
    default: {
      displayName: "the foobinator",
      name{
        givenName: "foo",
        familyName: "bar"
      },
      emails: [
        {value: "foo@gmail.com", type: "home"},
        {value: "foo@yahoo.com", type: "work"},
        {value: "bar@yahoo.com", type: "other"}
      ]
    }
  },
  documentSchemas: {
    default: "http://portablecontacts.net/draft-spec.html"
  }
}

Notifications

Notifications will be sent for modifications to the people storage such as adding a new person. The subject of the notification is usually the GUID of the item. The following list shows the function and the notification topic sent with the subject data.

add: "people-add" (guid)
update: "people-update" (guid)
remove: "people-before-remove" (guid)
remove: "people-remove" (guid)
changeGUID: "people-guid-change" ({ from: from, to: to })

The notification for remove will notify first before removing the item and afterwards, so listeners of people-before-remove can still get the item before it disappears.

If there are multiple items removed from a single remove call, each item will have its own notification.

Performance

  • Certain well-known fields (e.g., "firstname") would be automatically indexed by the component, so that find() can do quick lookups for the common case.
  • Lookups including slow fields would have to be slower. We can selectively add more indexes later on if needed.
  • add/remove/replace would be as fast as sqlite can be
  • Should use asynchronous calls to improve user experience - this means callbacks though (or we need to process events in the meantime)

Security

  • this is an internal API only.

Testing

We should create unit tests, though those tests do not need to go into each extension.

References