[Evolution-hackers] notes on how to write a connector



These are against Evo 1.4 and a lot of it will change for 2.0, but this
may help people who are working on things now...


How to write an Evolution backend
=================================

Note on terminology:

	component
		An executable or shared library that exports some
		functionality via CORBA interfaces

	shell component
		A component that implements the
		"IDL:GNOME/Evolution/ShellComponent:1.0" interface

	backend
		A component that implements the calendar or
		addressbook factory interface.

	connector (lowercase 'c')
		Sometimes used to refer to a backend that is not part
		of the main Evolution source tree

	Connector (capital 'C')
		The Ximian Connector for Microsoft Exchange



Startup
-------

When evolution starts up, it launches all available shell components
(in the order of their "evolution:shell_component_launch_order"
attribute values). The shell component interface is intended for the
user-interface components that represent folders/data/etc, and using
it for backends is basically a kludge. But by implementing the
ShellComponent interface, backends get three things:

	1. They are started up when the shell starts up, and notified
	   when the shell exits.
	2. They're given a handle to the GNOME_Evolution_Shell object
	   via the EvolutionShellComponent "owner_set" signal.
	3. They show up as icons in the startup splash screen

#1 is really only needed for components that implement out-of-process
mail backends (ie, Connector), since otherwise the camel provider
would have no way to launch the backend itself. For addressbook and
calendar backends, the UI (or palm sync daemon, etc) will launch the
backend as needed. However, being launched as a shell component
provides it with a single place to do all of its initialization.

#2 is not really needed at all; the component could just call
bonobo_activation_activate_from_id on "OAFIID:GNOME_Evolution_Shell"
with the EXISTING_ONLY flag.

#3 was originally a bug, but seems to be regarded as a feature now.
The backend's .server file should specify an
evolution:shell_component_icon attribute pointing to the icon that
should be used. You must either provide a full path, or make sure that
the icon gets installed in *evolution*'s images directory
("pkg-config --variable=imagesdir evolution-shell").


The mail, addressbook, calendar, and summary components are all shared
libraries. evolution/my-evolution/component-factory.c is a good
example of how to do shared-library component initialization.
evolution-groupwise/storage/main.c shows how to do out-of-process
component initialization.

Since EvolutionShellComponent was intended for components that
implement folder types, you *must* pass a list of folder types to
evolution_shell_component_new(), even if you don't implement any
folder types. In that case, just pass a list containing a dummy folder
type (as in evolution-groupwise/storage/main.c), and NULL for all of
the function pointer arguments. If your backend does need to implement
its own folder types (eg, the SunONE connector's "Invitations"
folder), then you would specify those here, and pass pointers to
functions to create views, etc. (evolution/my-evolution/
component-factory.c provides a simple example of creating views.
evolution/mail/component-factory.c provides a more complicated
example.)


The majority of the backend's startup (other than calling library
initialization functions and registering CORBA factories) should
happen in the "owner_set" signal handler. However, it should not do
anything gui-related until it receives the "interactive" signal. (This
is to prevent the case where a component pops up a dialog and then the
main evolution window opens on top of the dialog.) The "interactive"
signal handler will be passed a GdkNativeWindow pointer that can be
used with some functions (such as e_notice_with_xid() and
e_dialog_set_transient_for_xid() in evolution/e-util/e-dialog-utils.h)
to make sure that the backend's dialogs are properly associated with
the main evolution window.

Note that for both the "owner_set" handler and the "interactive"
handler, the shell will wait for your component to return before
continuing startup. If you have a lot to do, or need to do something
that may block, you may want to do it from an idle handler.



Storages
--------

To show up in the folder tree, a backend creates an EvolutionStorage
object and then calls evolution_storage_register_on_shell(). You can
create as many storages as you want. Folders are created with
evolution_storage_new_folder(), and can be added to the storage before
or after registering it.

The URIs that you use when creating the folders will be passed back to
you later via the calendar, addressbook, and camel "get folder"
interfaces, so they need to contain enough information that you will
be able to find the right folder when you see that URI later. There
are a handful of semi-conflicting constraints here:

	* Evolution only allows one backend of each type using a given
          URI scheme, so you shouldn't use "http:" for your folder
          URLs. You need to create your own protocol such as
	  "exchange:" or "webcal:".

	* The URIs of your evolution folders do not necessarily need
          to map to "real" URIs in any immediately obvious way, as
          long as your backend can figure out what they mean. You
          can have a folder whose real URI is:
		http://foogroupware.bar.com/~bob/Calendar/
	  and whose evolution URI is:
		foo://bob bar com/My Folders/Calendar/
	  or even:
		foo:ef29b1af-72c1-42eb-a6be-9250b0abc947

	* However, URIs may be visible in the GUI in some cases, so
	  they shouldn't be too ugly-looking (like the UUID example
	  above, or "like%20this"), and they MUST NOT contain
	  password information.

In addition, if the backend allows the user to configure multiple
accounts, it needs to be able to figure out which account a given URI
is associated with. (But if you're using libsoup to access an
HTTP-based server, you most likely cannot support multiple accounts,
because soup will end up using the wrong passwords at the wrong times.
This will be fixed in libsoup 2.2.)


The folder types used when adding folders to the storage should be the
standard folder types ("mail", "contacts", "calendar", and "tasks"),
or any custom folder type that you declared that your shell component
supports. You can also append "/public" to the folder type (eg,
"mail/public"), to mark it as being a "public folder". This currently
has three effects:

	* You can't pick a public folder as one of your default
          folders.

	* You can't accept a meeting request into a public calendar.

	* A public mail folder will have "Post Message" as its default
	  "New"-button action instead of "New Message".


If you pass TRUE for the has_shared_folders argument to
evolution_storage_new(), then the storage will be available in the
File -> Open Other User's Folder command and you must handle the
shared-folder-related signals on EvolutionStorage.


To do the ask-for-a-password-when-the-user-opens-the-storage trick, you
call evolution_storage_has_subfolders() to tell the shell that there
are subfolders you aren't telling it about yet, and it will call back
(via the storage's "open_folder" signal) when the user tries to open
the storage. See evolution/mail/component-factory.c for an example.
This can also be used when you want to fill in the folder tree only as
the user traverses it (as with Connector public folders).


You can add items to the folder context menu by calling
evolution_storage_add_property_item().
evolution/shell/evolution-test-component.c shows how to do this. There
is not currently any way to add an item to some folders in a storage
but not others.



Configuration
-------------

To add a page to the Settings dialog, implement the
"IDL:GNOME/Evolution/ConfigControl:1.0" interface. There are numerous
examples of this in the Evolution source tree. As with the splash
screen icon, the config control icons must either be specified by full
path in the .server file, or they must be installed in evolution's
images directory.

Connector also uses EAccountList (evolution/e-util/e-account-list.c)
to "spy" on the mailer's configuration information to see when
accounts are added, removed, or changed.

You can override the mailer account configuration part of the
first-time wizard by implementing the
"IDL:GNOME/Evolution/StartupWizard:1.0" interface and having a lower
evolution:startup_wizard:priority attribute value than the mailer
does. (Connector does this to provide a configuration wizard that
autodetects some account information by looking it up in Active
Directory.) evolution/mail/mail-config-druid.c contains the code for
the mailer's implementation (evolution_mail_config_wizard_new() et
al), although it's all mixed in with the normal account wizard code.



Calendar/Tasks
--------------

To be a calendar backend, a component must implement the
"IDL:GNOME/Evolution/Calendar/CalFactory:1.0" interface. Subclass
CalBackend and then create a CalFactory for that class. The file
backend (evolution/calendar/pcs/cal-backend-file.c) can be used as a
reference. evolution/wombat/wombat.c shows how to initialize the
calendar factory at startup.



Contacts
--------

Addressbook backends are similar to calendar/task backends. Declare
that you implement "IDL:GNOME/Evolution/BookFactory:1.0", subclass
PasBackend, and create a PasBookFactory. There are two addressbook
backends in the main evolution source tree,
evolution/addressbook/backend/pas/pas-backend-file.c and
pas-backend-ldap.c. evolution/wombat/wombat.c shows how to initialize
the addressbook factory at startup.



Mail
----

Most mail backends can be implemented as just Camel providers, and
there are several examples in evolution/camel/providers/. The provider
must be installed into evolution's camel provider directory
(pkg-config --variable=camel_providerdir camel), and include a .urls
file showing what protocols the provider handles.

Theoretically (this has never been tried), it should be possible to
subclass CamelImapStore and CamelImapFolder to create a provider that
uses IMAP, but where the folders are part of an EvolutionStorage
created by another component, that also contains calendar and contact
folders. Such a provider would have to have the
CAMEL_PROVIDER_IS_EXTERNAL flag set so that the mail component
wouldn't try to create a folder tree for it itself. (One known problem
with this is that you will need to call "mail_note_store (store, NULL,
NULL, NULL, NULL)" from your provider in order for vfolders to work.
This is a hack and should have been fixed, but things will work
differently in 2.0, so...).


Connector uses a provider that talks to the Connector backend process
via a custom protocol over a socket in /tmp, so that all of the
WebDAV-using code is in the backend.



Compilation
-----------

Evolution installs four .pc files that you will need to use to build
an evolution component of any sort.

	* camel: For camel providers

	* evolution-addressbook: For addressbook backends or clients

	* evolution-calendar: For calendar backends or clients

	* evolution-shell: For anything that implements
          EvolutionShellComponent, EvolutionStorage, or
          EvolutionConfigControl.

You can also use "pkg-config --variable=... evolution-shell" to find
various other information:

	privlibdir - location of evolution libraries
	privincludedir - location of evolution includes

	privlibexecdir - location of private binaries
	componentdir - location of shell components
	evolutionuidir - location of bonobo UI files (menu data)
	imagesdir - location of various icons

	idldir - location of evolution IDL files
	IDL_INCLUDES - flags to pass to orbit-idl when compiling
		files in idldir

See evolution-groupwise for a complete example.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]