On Fri, 2012-10-26 at 21:04 +0200, Patrick Ohly wrote:
> On Mon, 2012-09-17 at 11:32 +0200, Patrick Ohly wrote:
> > On Wed, 2012-09-12 at 14:51 +0200, Patrick Ohly wrote:
> > > I have some partial implementation of the API ready and will push it
> > > shortly to the new SyncEvolution git repo on freedesktop.org
> >
> > The code is now in the for-master/pim branch, see the
> > src/dbus/server/manager directory. The API text is in
> > src/dbus/server/pim/pim-manager-api.txt. I decided to avoid using IVI in
> > the code because it might turn out to be useful elsewhere.
>
> The code now fully implements the proposed API and has been tested by me
> for a while. The nightly testing includes the testpim.py tests. Manual
> testing with a Nokia N97 Mini also passed (with the caveats below). This
> is a good time to make it available to people who do not want to compile
> source from a git repo.
>
> Therefore I have merged the code into the master branch, tagged it as
> 1.3.99.1 and prepared the usual source .tar.gz:
> http://downloads.syncevolution.org/syncevolution/sources/syncevolution-1.3.99.1.tar.gz
>
> The benefits for traditional users are fairly small (except perhaps for
> the new caching mode) and the binaries would not contain the IVI
> features anyway, because the binaries target EDS < 3.6 and older
> distros. For these reasons I am not publishing binaries of 1.3.99.1.
>
> There are a few known issues regarding the PIM Manager in this release:
>
> - updating a contact is inefficient and can cause deadlocks in EDS:
> http://bugs.freedesktop.org/show_bug.cgi?id=55925
>
> - some minor memory leaks:
> http://bugs.freedesktop.org/show_bug.cgi?id=56396
>
> - in violation of the API spec, colon and upper case characters in the
> peer UID are not properly supported and should be avoided for now; I'm
> undecided whether I should fix the API to match the implementation or
> make the implementation more complicated to support the proposed API:
> http://bugs.freedesktop.org/show_bug.cgi?id=56436
All of these issues are resolved in SyncEvolution 1.3.99.2, which is
available from git and syncevolution.org now.
> A full overview of issues is here:
> https://bugs.freedesktop.org/showdependencygraph.cgi?id=55916&display=web&rankdir=TB
>
> I'm attaching the current API revision, a README which gives
> SyncEvolution specific information and two example scripts. All of this
> is also in the release and in the git repo.
Again I'm attaching the new API and SyncEvolution README for its
implementation. Instead of the examples (which you can get from git or
the source archive) I'm attaching the NEWS entry for 1.3.99.2.
> Regarding documentation, I'm still undecided whether specific peer
> config options, search capabilities and ordering really should be
> specified in the API documentation. I'm inclined to make this completely
> implementation specific and thus at best give examples in the API
> documentation. App developers need to consult their system documentation
> anyway to learn which version of the API is supported by the system.
As hinted above, I've restructured the documentation so that the
abstract API is covered by one document and the implementation by
another.
--
Best Regards
Patrick Ohly
Senior Software Engineer
Intel GmbH
Open Source Technology Center
Pützstr. 5 Phone: +49-228-2493652
53129 Bonn
Germany
SyncEvolution 1.3.99.2, xxxxxxxxxx
==================================
Another development snapshot. Includes all fixes that went into 1.3.2
and several improvements to the PIM Manager. Documentation was updated
and extended considerably. The pim-manager-api.txt now describes the
abstract API while src/dbus/server/pim/README explains how
SyncEvolution implements it.
Details:
* PIM Manager searches for a caller ID ('phone' search) in EDS
directly while folks still starts up. No unification is done of
these results. Intermediate results are replaced by the final ones
from folks once those are ready.
* PIM Manager: allow configuration of session directories (part of FDO #55921)
Useful for moving the session directories to a temporary file system.
They are essentially just useful for debugging when used as part of
PIM Manager.
- "logdir" - a directory in which directories are created with
debug information about sync session
- "maxsessions" - number of sessions that are allowed to exist
after a sync (>= 0): 0 is special and means unlimited,
1 for just the latest, etc.;
old sessions are pruned heuristically (for example,
keep sessions where something changed instead of
some where nothing changed), so there is no hard
guarantee that the last n sessions are present.
* PIM Manager: write less data to disk (part of FDO #55921)
Avoid writing config file changes to disk by enabling a new
"ephemeral" mode for syncing via the PIM Manager. In this mode,
config file changes are not flushed resp. discarded directly.
This prevents writing to .ini files in ~/.config.
The "synthesis" binfile client files are still written, but they get
redirected into the session directory, which can (and should) be set
to a temp file system and get deleted again quickly.
Data dumps are turned off now in the configs created by the PIM
Manager.
* syncevo-dbus-server: use syslog instead of standard output by default
* syncevo-dbus-server: command line options for controlling
output and startup
-d, --duration=seconds/'unlimited' Shut down automatically
when idle for this duration (default
300 seconds)
-v, --verbosity=level Choose amount of output, 0 = no output,
1 = errors, 2 = info, 3 = debug;
default is 1.
-o, --stdout Enable printing to stdout (result of
operations)
and stderr (errors/info/debug).
-s, --no-syslog Disable printing to syslog.
-p, --start-pim Activate the PIM Manager (= unified
address book)
immediately.
* PIM Manager: store set of active address books persistently (FDO #56334)
Together with storing the sort order persistently, this allows
restarting the daemon and have it create the same unified address book
again.
* PIM Manager: remove colon from valid peer UID character set (FDO #56436)
Using the UID as part of file names gets more problematic when
allowing colons. Remove that character from the API and enforce
the format in the source code.
* PIM Manager API: introduce contact ID and use it for reading
This makes it easier for a client to fully polulate its view with
contact data. Previously it could happen that due to concurrent
changes in the server, a client was returned data for the same
contact multiple times. A client had to detect that and re-issue
read requests.
* PIM Manager API: optional ViewAgent.Quiescent() (FDO #56428)
The callback is guaranteed to be invoked once when a search has
finished sending its initial results, and not sooner. This makes it
possible to check whether the current data contains some contact or
not.
* PIM Manager: limit number of search results (FDO #56142)
A 'limit' search term with a number as parameter (formatted as string)
can be added to a 'phone' or 'any-contains' search term to truncate the
search results after a certain number of contacts. Example:
Search([['any-contains', 'Joe'], ['limit', '10']])
=> return the first 10 Joes.
As with any other search, the resulting view will be updated if
contact data changes.
The limit must not be changed in a RefineSearch(). A 'limit' term may
(but doesn't have to) be given. If it is given, its value must match
the value set when creating the search. This limitation simplifies the
implementation and its testing. The limitation could be removed if
there is sufficient demand.
* PIM Manager: fix refining a search
Due to not mapping the local index in the view to the parent's index,
refining only worked in views where parent and child had the same
index for the contacts in the search view.
* PIM Manager: fix starting when done via search
When the unified address book (= FullView) was not running yet at the
time when a client wanted to search it, the unified address book was
not started and thus the search never returned results.
* PIM Manager: fix writing contact, support photo and notes
folks and EDS do not support writing properties in parallel
(https://bugzilla.gnome.org/show_bug.cgi?id=652659). Must serialize
setting of modified properties.
* PIM Manager: fix incorrect contact removal signals in filtered view
The filtered view did not check whether a parent's removed contact was
really part of the view before sending a removal signal for it.
* D-Bus: missing out parameters in D-Bus introspection XML (FDO #57292)
The problem was in the C++ D-Bus binding. If the method that gets bound
to D-Bus returns a value, that value was ignored in the signature:
int foo() => no out parameter
It works when the method was declared as having a retval:
void foo (int &result) => integer out parameter
This problem existed for both the libdbus and the GIO D-Bus
bindings. In SyncEvolution it affected methods like GetVersions().
* PIM Manager performance: pre-compute normalized telephone numbers
Looking up by phone number spends most of its cycles in normalizing of
the phone numbers in the unified address book. Instead of doing that
work over and over again during the search, do it once while loading.
Looking up a phone number only once does not gain from this change, it
even gets slower (more memory intensive, less cache locality). Only
searching multiple times becomes faster.
Ultimately it would be best to store the normalized strings together
with the telephone number inside EDS when the contact gets
created. Work on that is in progress.
* PIM Manager: improve performance of FullView sorting
This fixes the hotspot during populating the FullView content: moving
contacts around required copying IndividualData and thus copying
complex C++ structs and strings. Storing pointers and moving those
avoids that, with no lack of convenience thanks to boost::ptr_vector.
Reordering also becomes faster, because the intermediate copy only
needs to be of the pointers instead of the full content.
* PIM Manager example: add benchmarking
The new "checkpoints" split up the whole script run into pieces which
are timed separately, with duration printed to stdout. In addition,
tools like "perf" can be started for the duration of one phase.
* EDS: fix creating databases
--create-database was broken in combination with the final code in EDS
3.6 because it passed NULL for the UID to e_source_new_with_uid(),
which is considered an error by the implementation of that
method. Must use e_source_new() if we don't have a UID.
* fixed some memory leaks, extended tests to cover new features and bugs
Preamble
========
This text describes a D-Bus API. The API implements in-vehicle infotainment
(IVI)
use cases around contacts:
- cache address books from peers (primarily phones connected via Bluetooth)
in local address books
- provide a unified address book that combines a configurable (and changing)
subset of the local address books
- fast phone number lookup
- browsing and searching in the unified address book
Tasks that are expected to be done by the user of this API:
- identify peers and their capabilities
- decide how and when peer data should be cached
- define which data goes into the unified address book
In other words, the API provides the mechanisms and the user the
policy.
Several aspects of this API may differ depending on the
implementation. For example, searching for contacts and syncing with
peers are not described in the API. Consult the documentation of the
API implementation to learn what it supports. For SyncEvolution, that
documentation is the src/dbus/server/pim/README file.
Datatypes
=========
Peers
-----
A peer is an entity which has exactly one address book that is meant
to be cached locally. Typically a peer is a phone connected via
Bluetooth and accessed via PBAP, but it could also be a web service
that supports CardDAV or a phone with SyncML support.
Peers are identified by a unique string ID. That ID needs to be
assigned by the user of this API. The string must not be empty and may
only contain characters a-z, 0-9 and hyphen. No other assumptions
about its content are made. For example, the phone's Bluetooth MAC
address could be used after removing or replacing the colon and using
lower case hex characters.
For an entity that has more than one address book, multiple peers must
be configured.
For each peer, enough information must be provided to access its
address book. That information is passed via D-Bus as a
string-to-string dict, with keys and their values being defined by the
implementation.
Address books
-------------
Address books which mirror data from a specific peer use the string
"peer-<uid>" as ID, where <uid> is the unique ID of that peer.
In addition, there is a system address book which is independent of
any particular phone. Its ID is the empty string.
This naming scheme can be extended later on, to support other kinds
of address books.
Contact
-------
A single contact is transferred via D-Bus as a string->variant dict
where the keys are predefined property names and the values represent
simple values (a string for "full-name") or more complex structures
(list of phone numbers for "phone-numbers", with each list entry
itself being a combination of type flags and the actual value).
[comment: this mirrors the properties of a libfolks Individual:
http://telepathy.freedesktop.org/doc/folks/c/FolksIndividual.html]
Some properties of a FolksIndividual only make sense locally and are
not transmitted, for example the personas it is derived from.
Some other properties provide information not found that way in
FolksIndividual:
- "source" = list of string pairs, where each pair is a combination
of address book ID and local contact ID inside that address book
(not necessarily the same as the vCard UID of a contact!)
- "id" = an opaque string which identifies the contact while it
exists inside any PIM Manager view. See ContactsAdded and ReadContacts.
Property values which are large (like photos) are not sent via
D-Bus. Instead a link to a local file is sent.
For a full definition of contact properties see the implementation
documentation.
Search results
--------------
The goal is to support a UI which:
- displays an ordered list of the search result,
- can show the initial results with minimal delay,
- can load actual content for the display as needed (only
load the parts which are visible or will be visible soon).
The content of the unified address book can change at any time. The
API design takes that into account by using a model/view/controller
model.
The model is the complete list of contacts, sorted according to the
currently configured sort order. Sorting is part of the model to
simplify generating views.
The view is the subset of the data that a user of the API has
requested. In the most extreme case, all contacts are part of the
view. Therefore contact data has to be requested explicitly. Contacts
are numbered 0 to n-1 in each view, where n is the number of contacts
in the view. Sort order is the same as in the underlying model. Change
notifications with these index numbers are sent as contacts are added,
modified or removed.
The controller is the part of the API which allows changing contacts
in the system address book, changing the sort order, enabling or disabling
address books, etc.
Note that removing or adding a contact changes the numbers assigned to
other contacts. Example:
- A view containing 10 contacts is created.
- A notification about "contacts #0 to #9 added" is sent
(given as pair of first index and count, not list of numbers).
- The five contacts starting with #5 to are read via their ID.
- Contact #4 gets removed. The user needs to remember that
the data that it has now corresponds to contacts #4 to #8.
- Contact #5 gets added, before the contact which had that
number before. The user now has contacts #4 and #6 to #9.
It should request contact #5 if (or once) it is needed to
provide a complete list to the user.
[comment: using a view could be simplified by including contact data
in the change notifications. This is not planned at the moment because
it would not work well for large views. When adding it, there should
be an API to restrict which properties of a contact get sent.]
Error handling
==============
D-Bus error messages are not localized. They are meant for debugging,
not for displaying to the user. In cases where the caller may be able
to do something about an error, specific error codes are defined as
part of the API. However, typically errors are generic and the caller
simply has to assume that the PIM storage is currently unusable.
Unless noted otherwise, calls return when the requested operation is
complete.
The following errors are defined. In addition to the D-Bus name of the
error they provide a textual error description.
org._01.pim.contacts.Manager.Aborted
Some operation was intentionally aborted instead of letting it
complete. Typically not an error.
org._01.pim.contacts.Manager.BadStatus
A generic error report. The error description is a string
which gives further information for debugging.
API
===
PIM Manager
-----------
The PIM manager is used to hold the unified address book in memory,
create views on it, change configuration and control data transfers
from phones.
Service: org._01.pim.contacts
Interface: org._01.pim.contacts.Manager
Object path: /org/01/pim/contacts
Methods:
void Start()
The PIM manager does not start loading contact data right
away. That allows setting the options like sort order first
and/or delaying the loading until it is needed. After
Start(), changing options that affect the unified address
book will take effect immediately.
Calling Start() is optional, any method asking for data will
automatically do that.
void Stop()
Explicitly tells the PIM manager to discard the unified address
book and free up the memory if possible (= not currently in use).
Primarily useful for testing.
void SetSortOrder(string mode)
"mode" must be one of the values supported by the implementation.
string GetSortOrder()
Returns the current sort order.
list of strings GetActiveAddressBooks()
Returns the IDs of the address books which currently
contribute to the unified address book.
void SetActiveAddressBooks(list of strings)
Sets the address books which contribute to the unified
address book.
void SetPeer(string uid, dict properties)
Adds or modifies a peer. Modifying a peer does *not* affect
any contact data which might be cached for it.
void RemovePeer(string uid)
Removes a peer and all its cached data. If that data was
part of the active address books, it will be removed
automatically.
void SyncPeer(string uid)
Retrieve contacts from the peer and ensure that the local
cache is identical to the address book of the peer. The call
returns once the operation is complete. Only if there was no
error can the caller assume that the cache is up-to-date.
Otherwise it is in an undefined state.
void StopSync(string uid)
Stop any running sync for the given peer. The SyncPeer() method
which started such a sync will return with an "aborted" error
once the sync was stopped.
dict of UID to string key/value dict GetAllPeers()
Returns information about all currently configured peers.
object Search(list filter, object agent)
Creates a new view which contains all contacts matching the
filter. The call returns the object path of a view object
after validating parameters and starting the result
gathering, and before completing the search. The view object
can be used to control the view via the
org._01.pim.contacts.ViewControl interface.
The content of the filter is defined by the implementation.
Notifications for the view are sent back to the caller by
invoking methods from the org._01.pim.contacts.ViewAgent
interface on the object whose path is given in the "view"
parameter. If any of these method calls fail, the view will
automatically be destroyed.
In other words, the caller first needs to get ready to process
results by registering an object on the bus before calling
Search().
[comment: this allows sending results to just one recipient,
something that cannot be done easily with the use of signals as in,
for example, obexd. In obexd, the initiator of a transfer
has to subscribe to org.bluez.obex.Transfer on the object path
returned to it when starting the transfer, then check the current
status before waiting for signals, because the "Completed" signal
might have been sent before it could register for it.]
string AddContact(string addressbook, dict contact)
Adds a new contact to the given address book. Typically
only the system address book is writable. Contact properties
which are unknown or cannot be stored are silently ignored.
Returns the local ID of the new contact in the address book.
Photo data that is sent inline in the dict will be split out
into a file that gets associated with the contact. A photo
file that gets linked will continue to be owned by the
caller; the contact storage may or may not make a copy of it,
depending on which storage is used.
void ModifyContact(string addressbook, string localid, dict contact)
Updates an existing contact.
void RemoveContact(string addressbook, string localid)
Remove the contact and all of its associated data (like the
photo, if the photo file is owned by the contact storage).
Service: org._01.pim.contacts
Interface: org._01.pim.contacts.ViewControl
Object path: [variable prefix]/{view0,view1,....}
Methods:
list of (int index, contact dicts) pairs ReadContacts(array ids)
Requests the data of the contacts idenfified via their IDs.
Only the data of contacts that are still part of the view
can be returned.
The returned list contains the current index of the
requested contact plus its data. -1 and an empty
dictionary are returned for contacts which can no longer
be read, for example because they were removed from the
view in the meantime or because the ID was simply
invalid.
Note that the caller must process the call response after
all events via the ViewAgent interface. Otherwise the
index numbers are potentially out of sync and thus
unreliable. Doing this call asynchronously and dealing
with the response as part of the main event loop will do
the right thing automatically, because D-Bus guarantees
ordering of messages.
Making this explicit by returning data via another
org._01.pim.contacts.ViewAgent method was considered and
rejected a) for the sake of keeping this API simple and
b) to allow simple synchronous calls where it makes sense
(testing, for example).
void Close()
Closes the view and all resources associated with it.
Pending ReadContacts() calls will return without any
data and no error.
void RefineSearch(list filter)
Replaces the current filter of the view with a new one.
The new filter must be stricter than the old one. Contacts
which were already filtered out will not be added back
to the view when setting a less restrictive filter (simplifies
the implementation and improves performance).
Service: [user of the PIM Manager]
Interface: org._01.pim.contacts.ViewAgent
Object path: [as chosen by user of PIM Manager]
Methods:
void ContactsModified(object view, int start, array ids)
Contacts #start till #start + count (inclusive) have
changed. Data that the recipient of the call might have
cached became invalid and should be reloaded.
It is possible that a contact gets replaced by another
with a single "contact modified" signal. In other words,
the ID at each position may change and thus the IDs
are sent as part of the signal.
In cases where a contact changes its position in the
view, both a combination of "contact removed" + "contact
added" (single contact changes) as well as several
"contact modified" signals are possible (contacts swap
position, for example when reordering).
In the later case, a contact will temporarily appear
at two different positions.
void ContactsAdded(object view, int start, array ids)
New contacts were added to the view. The number
of new contacts is given via the size of the ids array.
The ID of each new contact is guaranteed to be the same
in all views. IDs may get reused after their contact got
removed from the last view it was contained in. In
particular there is no guarantee that it is persistent
across restarts of the PIM manager.
The contact which previously had index #start now
has index #start + count, etc.
void ContactsRemoved(object view, int start, array ids)
Some contacts were removed from the view.
The contact which previous had index #start + count
(if there was one) now has index #start, etc.
void Quiescent(object view)
The current content of the view is complete. No further
updates are expected until something changes again
(underlying data, ordering, active address books,
filter).
Changing data (directly or via syncing) can trigger
multiple "Quiescent" signals, depending on when these
changes are reported by the underlying storage.
Changing multiple settings will trigger one "Quiescent"
per change.
Implementing the Quiescent() method in a ViewAgent
is optional.
Overview
========
The code in this directory only gets compiled when SyncEvolution is
configured with --enable-dbus-service-pim. It uses libfolks and the
PBAP backend to implement a unified address book. This additional
functionality is provided via the org._01.pim D-Bus API.
That API is independent of libfolks and SyncEvolution. That it is
implemented inside syncevo-dbus-server merely simplifies the
implementation, by reusing some code provided by SyncEvolution
and the core Server class:
* C++ D-Bus bindings
* logging
* D-Bus server life cycle control: delay shut down while
clients make calls, inhibit shutdown while clients are
registered, restart when files change on disk during system
update, ...
* direct access to SyncEvolution instead having to go through
the http://api.syncevolution.org D-Bus API
Compilation
===========
Use --enable-dbus-service-pim --enable-pbap --enable-ebook.
--enable-ebook is already the default at the moment. It must not be
disabled.
The PBAP backend can be disabled. However, then fetching data from
phones via PBAP obviously does not work.
More information
================
Public discussion started here, comments can be sent via the gmane
"reply" feature:
http://comments.gmane.org/gmane.comp.mobile.syncevolution/4009
Issues specific to PIM Manager can be found in this graph:
https://bugs.freedesktop.org/showdependencygraph.cgi?id=55916&display=web&rankdir=TB
Dependencies
============
A fairly recent libfolks > 0.7.4 is required. The 837a88 commit
is required and several other pending changes are recommended:
https://bugzilla.gnome.org/show_bug.cgi?id=686693
writing birthday lacks conversion from UTC
https://bugzilla.gnome.org/show_bug.cgi?id=685401
linking by email
https://bugzilla.gnome.org/show_bug.cgi?id=686695
support nickname in add_persona_from_details
Other requirements:
* Evolution Data Server >= 3.6
* boost::locale and libphonenumber (when using the default
sorting and searching)
* glib >= 2.30
* Python >= 2.7 (only for testing)
Known issues
============
None at the moment.
Extending the implementation
============================
Sorting and searching can be replaced with a different implementation
at compile time via --enable-dbus-service-pim[=<locale>]: it uses the
file src/dbus/server/pim/locale-factory-<locale>.cpp to implement
sorting and searching.
Any additional library dependencies for that file need to be added to
the main configure.ac.
The default implementation is based on boost::locale and
libphonenumber and is described below.
Sorting
=======
The sort order can be "first/last", "last/first", "full name".
"first/last" sorts based on the first name stored in the "name"
property, with the last name used to break ties between equal
first names.
"last/first" reverts that comparison.
"full name" sorts based on the full name chosen for the contact if
there is such a string, otherwise it uses the concatenation of the
individual name componts without prefix (= "[<first name>]
[<middle name>] [<last name>] [<suffix>])" as fallback. In other
words, it sorts correctly when either all contacts have a full
name explicitly set or the full names that were set following the
same pattern as the fallback.
Sorting is case-insensitive. The default is "last/first" if not set
earlier.
Searching
=========
Supported searches:
[ ] - An empty list matches all contacts.
[ ['phone', '<number>'] ] - Look up a valid phone number (= "caller ID").
The country code for the current locale is added if no country
code was given in the number. Phone numbers in the unified address
book must start with the resulting full number, after being normalized
the same way.
In other words:
- Formatting does not matter.
- Alpha characters are aliases for numbers on the keypad and match
their corresponding number.
- Additional digits in the address book are ignored, only
the prefix must match (extensions may or may not be included in
<number>).
- Phone numbers in the address book which cannot be normalized
cannot be matched.
[ ['any-contains', '<text>', <flags>] ] - Sub-string search for <text>
in the following contact values: first, middle or last name,
formatted name, nick name, phone number (sub-string search which ignores
formatting and treats alpha characters as aliases, in contrast
to prefix match in 'phone') or email address. Optional flags include:
'case-insensitive' (the default), 'case-sensitive'.
Using a list will allow extending the search capabilities later,
for example by allowing multiple terms which all must match
and/or adding recursive queries like this:
['and',
['or', ['any-contains', 'joe'], ['any-contains', 'joan']],
['phone', '1234']
]
Lookup and search are different: the former is based on a valid
number, the later on user input.
A 'phone' lookup can compare normalized numbers including the country
code, to ensure that the lookup is exact and does not mismatch numbers
from different countries. Heuristics like suffix matching do not do
this correctly in all cases.
An 'any-contains' search is based on user input, which might contain
just some digits in the middle of the phone number. The search ignores
formatting in both input and address book.
In both cases, alpha characters are treated as aliases for their
corresponding digit on a keypad when matching phone numbers.
In addition, an empty string as sort order picks a simple, ASCII-based
"last/first" sorting. This is used for testing.
A 'limit' search term with a number as parameter (formatted as string)
can be added to a 'phone' or 'any-contains' search term to truncate the
search results after a certain number of contacts. Example:
Search([['any-contains', 'Joe'], ['limit', '10']])
=> return the first 10 Joes.
As with any other search, the resulting view will be updated if
contact data changes.
The limit must not be changed in a RefineSearch(). A 'limit' term may
(but doesn't have to) be given. If it is given, its value must match
the value set when creating the search. This limitation simplifies the
implementation and its testing. The limitation could be removed if
there is sufficient demand.
Phone number lookup
===================
A "phone" search must return results quickly (<30ms with 10000
contacts) under all circumstances, including the period of time where
the unified address book is still getting assembled in memory. To
achieve this, SyncEvolution searches directly in the active address
books and presents these results until the ones from the unified
address book are ready.
A quiescence signal will be sent when:
1. results from EDS are complete before the ones from the unified
address book
2. results from the unified address book are complete.
The first signal will be skipped and the EDS results discarded if EDS
turns out to be slower than the unified address book.
Results from different EDS address books are not unified, for the sake
of simplicity. They get sorted according to the sort order that was
active when starting the search. Changing the sort order while the
search runs will only affect the final results from the unified
address book.
Refining such a search is not supported because refining a phone
number lookup is not useful.
Peers
=====
The following keys are supported for the configuration of a peer:
- "protocol" - defines how to access the address book. "PBAB" (phone
book access protocol) and "file" (read vCard files from
directory) are implemented. "SyncML" and "CardDAV"
could be added easily.
- "transport" - defines how to establish the connection. The only
supported value is "Bluetooth" (for protocol=PBAP or
SyncML), which is also the default if not given
explicitly.
- "address" - the Bluetooth MAC address in the aa:bb:cc:dd:ee:ff
format (for transport=Bluetooth).
- "database" - empty or unset for the internal address book
(protocol=PBAP), or the path to the directory
(protocol=file).
- "logdir" - a directory in which directories are created with debug
information about sync session.
- "maxsessions" - number of sessions that are allowed to exist
after a sync (>= 0): 0 is special and means unlimited,
1 for just the latest, etc.;
old sessions are pruned heuristically (for example,
keep sessions where something changed instead of
some where nothing changed), so there is no hard
guarantee that the last n sessions are present.
Not supported via the API at the moment:
- selecting a specific phone address book
- selecting which vCard properties get cached
Contact Data
============
A contact dictionary has the following key/value pairs. More properties
may be added later.
contact dictionary:
"id" - string, see API description
"source" - string, see API description
"full-name" - string, formatted by the user or automatically (vCard FN)
"nickname" - string
"structured-name" - name dictionary (vCard N)
"photo" - string, the URL (usually of a local file; EDS strips all inline photo
data in vCards and puts them into local files, leading to URLs like:
file:///home/user/.local/share/evolution/addressbook/system/photos/pas_id_5012983E0000065A_photo-file0.image%2Fjpeg)
"birthday" - birthday tuple
"emails" - value list with strings as value
"phones" - value list with strings as value
"urls" - value list with strings as value
"notes" - list of strings; in practice only one entry is supported
"addresses" - value list with an address dictionary as value
"roles" - list of role dictionaries
name dictionary:
"family" - string, the last name
"given" - string, the fist name
"additional" - string, middle names
"prefixes" - string, name prefix like "Mr."
"suffixes" - string, name suffix like "Sr."
birthday tuple:
(yy, mm, dd) - integer values for year, month, and day
role dictionary:
"organisation" - main organization ("Foo ACME")
"title" - title inside that organization ("vice president")
"role" - role as part of that origanization ("adviser")
value list:
[ (value, [parameter, ...]), (value, ...) ]
value - depends on the property
parameter - a string, with values again depending on the property;
a value may have zero to n different parameters
phone parameters:
"voice"
"fax"
"car"
"cell"
"pager"
"pref"
"home"
"work"
"other" (might not be set explicitly)
address parameters:
"home"
"work"
"other" (might not be set explicitly)
url parameters:
"x-home-page"
"x-blog"
"x-free-busy" - public calendar free/busy URL
"x-video" - video chat URL
address dictionary:
"po-box" - string, post office box
"extension" - string, address extension
"street" - string, street name
"locality" - string, city name
"region" - string, area name
"postal-code" - string
"country" - string
Note: all of the strings and values above are defined by SyncEvolution
in individual-traits.cpp, except for parameter strings. Those come
directly from folks, more specifically AbstractFieldDetails:
http://telepathy.freedesktop.org/doc/folks/vala/Folks.AbstractFieldDetails.html
Here is an example contact dictionary, using Python syntax:
{
'full-name': 'John Doe',
'nickname': 'Johnny',
'structured-name': {'given': 'John', 'family': 'Doe'},
'birthday': (2006, 1, 8),
'photo': 'file:///home/user/file.png',
'roles': [
{
'organisation': 'Test Inc.',
'role': 'professional test case',
'title': 'Senior Tester',
},
],
'source': [
('test-dbus-foo', luids[0])
],
'id': 'xyz',
'notes': [
'This is a test case which uses almost all Evolution fields.',
],
'emails': [
('[email protected]', ['home']),
('[email protected]', ['other']),
('[email protected]', ['work']),
('[email protected]', ['other']),
],
'phones': [
('business 1', ['voice', 'work']),
('businessfax 4', ['fax', 'work']),
('car 7', ['car']),
('home 2', ['home', 'voice']),
('homefax 5', ['fax', 'home']),
('mobile 3', ['cell']),
('pager 6', ['pager']),
('primary 8', ['pref']),
],
'addresses': [
({'country': 'New Testonia',
'locality': 'Test Megacity',
'po-box': 'Test Box #3',
'postal-code': '12347',
'region': 'Test County',
'street': 'Test Drive 3'},
[]),
({'country': 'Old Testovia',
'locality': 'Test Town',
'po-box': 'Test Box #2',
'postal-code': '12346',
'region': 'Upper Test County',
'street': 'Test Drive 2'},
['work']),
({'country': 'Testovia',
'locality': 'Test Village',
'po-box': 'Test Box #1',
'postal-code': '12345',
'region': 'Lower Test County',
'street': 'Test Drive 1'},
['home']),
],
'urls': [
('chat', ['x-video']),
('free/busy', ['x-free-busy']),
('http://john.doe.com', ['x-home-page']),
('web log', ['x-blog']),
],
}
Configuration and data handling
===============================
The configuration of peers is mapped to normal SyncEvolution
configurations. Every peer gets its own context, which contains both
the local source definition for the peer and sync plus target configs.
The special name space prefix "pim-manager-" used to identify them and
avoid potential conflicts with normal SyncEvolution configurations.
Look in ~/.config/syncevolution/pim-manager-* to find the
configuration files.
Local EDS databases are created with a fixed UID and name that also
have that prefix. One could go one step further than it is currently
done and set these databases to "disabled" in the ESourceRegistry,
which would hide them in Evolution.
The SyncEvolution command line can be used to view or manipulate
these databases:
https://syncevolution.org/wiki/item-operations
The sort order and set of active address books are stored persistently
in ~/.config/syncevolution/pim-manager.ini as:
- "sort" = same value as in the API
- "active" = space, comma or tab separated EDS source UUIDs
Usage
=====
The PIM manager is part of the syncevo-dbus-server. It can be started
automatically via D-Bus or explicitly. When started automatically, it
logs error messages to syslog.
The PIM manager starts as idle and needs to be started via the D-Bus
Start() API before it begins assembling the unified address book.
SyncEvolution installs an XDG autostart file
(/etc/xdg/autostart/syncevo-dbus-server.desktop) which, on systems
supporting that mechanism, ensures that syncevo-dbus-server is started
once. This is used for automatic syncing, which is not active when
using only the PIM Manager. Therefore syncevo-dbus-server will
terminate again after some idle period.
It's recommended to disable this mechanism or patch
syncevo-dbus-server.desktop to start the server with different
options. In particular the "--start-pim" option may be useful to
start up the UI and the server in parallel.
Here is a list of supported options:
Help Options:
-h, --help Show help options
Application Options:
-d, --duration=seconds/'unlimited' Shut down automatically
when idle for this duration (default
300 seconds)
-v, --verbosity=level Choose amount of output, 0 = no output,
1 = errors, 2 = info, 3 = debug;
default is 1.
-o, --stdout Enable printing to stdout (result of
operations)
and stderr (errors/info/debug).
-s, --no-syslog Disable printing to syslog.
-p, --start-pim Activate the PIM Manager (= unified
address book)
immediately.
Limitations
===========
There are no hard-coded limits. In practice the number of contacts,
parallel searches, peers, etc. is limited by CPU performance, amount
of RAM and disk space.
Functionality
=============
Caching
-------
Caching of contacts is done roughly like this:
1. The PBAP backend reads all contact data. See src/backends/pbap.
2. As part of a local sync (see main SyncEvolution README), a local-cache-slow
sync is done. In that mode all local data is compared against the incoming
data. Matches are found according to the properties that are defined
as compare="always" or compare="slowsync" in
src/syncevo/configs/datatypes/00vcard-fieldlist.xml.
In the default file, these are the first, middle and last name and the
organization.
3. Local entries which have a match are updated with data from that match,
unmatched local entries are removed and unmatched incoming ones are
created.
This means that changing "less important" properties will be turned
into local updates. This has the desirable effect of not changing the
local ID of the existing contact, which might become important when
attaching local meta data to that contact via its local ID (not
supported at the moment; folks itself has a favorite flag which is
stored like that). It also reduces disk IO.
If matching fails, a contact will get deleted and recreated. The end result
in the unified address book is still the same, because folks does not
rely on the ID for linking.
Supported fields
----------------
The PBAP backend always downloads the entire vCard. It supports restricting
the vCard to specific properties, see src/backend/pbap/README. However, this
is not used by the PIM Manager.
In particular, PHOTO data is always included in the download right
away. A mode where caching only works without PHOTO in the initial
pass and then adds that in a second step could be added.
Internally a vCard is represented as a Synthesis field list (again,
see 00vcard-fieldlist.xml). X- extensions in the incoming vCard are
preserved.
Unified address book
--------------------
The unified address book is assembled by folks based on properties
that it considers "linkable". Linkable properties must be unique for a
person. A phone number is not linkable, because different persons
living at the same place might share a phone. For EDS, the linkable
properties are defined in folk's backends/eds/lib/edsf-persona.vala
and currently are:
- instant messaging handles
- email addresses
- local IDs (not used in this context)
- web service addresses (not used in this context)
folks supports heuristics for identifying persons which are likely to
be the same. This is not used by the PIM Manager. If it was, then links
would have to be established based on the local IDs and keeping those
stable became more important.
Localization
------------
The usual environment variables are used to determine the locale:
LC_CTYPE, LC_ALL, and LANG in that order (i.e. LC_CTYPE first and LANG
last). There is no support for changing that once syncevo-dbus-server
runs.
boost::locale is used for collation while sorting contacts. The
secondary collation level is used, which means that case, punctuation
are ignored while accents are relevant. This is hard-coded in
locale-factory-boost.cpp as DEFAULT_COLLATION_LEVEL.
Searching is either done with a strict text comparison (case
sensitive) or after using case folding (case insensitive, see
http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding).
A 'phone' lookup uses libphonenumber to compare the free-form text
field for telephone numbers against a caller ID. The text field must
be in a format that libphonenumber understands, otherwise it will be
ignored. A country code is optional. If missing, the country code of
the current locale will be added before the comparison.
Testing
=======
Tests are mostly written in Python. See test/test-dbus.py (base
classes and testing of the normal SyncEvolution D-Bus API) and
src/dbus/server/pim/testpim.py.
These operations manipulate the system address book. To avoid
unexpected data loss when a developer runs the script in his
normal environment, check that testpim.py gets run like this:
XDG_CONFIG_HOME=`pwd`/temp-testpim/config \
XDG_DATA_HOME=`pwd`/temp-testpim/local/cache \
PATH=<SyncEvolution install path>/bin:<SyncEvolution install
path>/libexec:$PATH \
<path to SyncEvolution source>/test/dbus-session.sh \
<path to SyncEvolution source>/src/dbus/server/pim/testpim.py
This will use temp-testpim in addition to temp-testdbus in the current
directory for temporary files.
If TEST_DBUS_PBAP_PHONE is set to the Bluetooth MAC address (like
A0:4E:04:1E:AD:30) of a phone, PBAP syncing with that phone is tested.
The phone must be paired, connected and support SyncML in addition to
PBAP. The test hard-codes Nokia SyncML settings to keep it simpe and
because Nokia phones are most likely to be used.
_______________________________________________
SyncEvolution mailing list
[email protected]
http://lists.syncevolution.org/listinfo/syncevolution