summaryrefslogtreecommitdiff
path: root/src/dbus/server/pim/pim-manager-api.txt
blob: 7b28643b937d2bb2e0ba8c17c6baf8dcda4b432f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
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 CreatePeer(string uid, dict properties)

         Creates a peer. Will fail with a
         org._01.pim.contacts.Manager.AlreadyExists error if the uid
         is already in use. To change the configuration of a peer,
         remove and recreate it. This ensures that its data gets
         removed, too.

         [A ModifyPeer() might get added if there is demand for it
         and the desired behavior (remove cached data or keep it?)
         is better understood.]

         As a backwards compatibility measure this method is
         also available as SetPeer() with the semantic that the
         config is created or modified automatically as needed.

    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).

        void ReplaceSearch(list filter, bool refine)

             Same as RefineSearch() if refine is true. If refine is false,
             the new filter can be less restrictive and contacts which
             did not match the old filter will be added back to the list
             of matching contacts.


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.