Camel.Store

CamelStore is the base object of any backends which support storage and retrival of mail. It is one of the more complicated objects that any plugin Evolution/Camel.Provider must implement, and it manages the connection and collections of folders.

Base class

The base class derives from Evolution/Camel.Service, and adds some state information.

 struct _CamelStore {
        CamelService parent_object;
        struct _CamelStorePrivate *priv;
        
        CamelObjectBag *folders;
 
        guint32 flags;
        guint32 mode;
 };
  • folders: Is a Evolution/Camel.Object#Camel.ObjectBag of all the active folders for this store. This is managed by the store method entry points, and should only be used in a read-only manner.

  • flags: Flags which define information about the folder for client or Camel use. See below.
  • mode: Permissions on the folder.

Flag bits, these are initialised by the implementation as appropriate. The default flags value is CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK.

  • CAMEL_STORE_SUBSCRIPTIONS: This means that the store supports subscriptions.
  • CAMEL_STORE_VTRASH: The store supports a virtual trash, or rather, the base class should implement the trash as a virtual folder.
  • CAMEL_STORE_FILTER_INBOX: The store should have it's inbox filtered. This is just a copy of a user-preference value, which is taken from the Evolution/Camel.URL used to initialise the service, the filter URL parameter. It isn't used anywhere else in Camel and probably doesn't belong here.

  • CAMEL_STORE_VJUNK: Similar to the CAMEL_STORE_VTRASH flag, this indicates that the base class should implement the junk folder as a virtual folder.

  • CAMEL_STORE_PROXY: Indicates that this is a proxy account. I can't remember why the flag is required in the base class.

Mode bits, by default they are both set.

  • CAMEL_STORE_READ: The folder content is readable.
  • CAMEL_STORE_WRITE: The folder may be appended to.

These mode bits are very coarse grained. Perhaps per-folder mode bits which match IMAP ACL permissions could also be added. (RFC-2086)

The class structure itself consists of a large number of virtual methods; most are explained in the following sections.

 typedef struct {
        CamelServiceClass parent_class;
 
        GHashFunc       hash_folder_name;
        GCompareFunc    compare_folder_name;
 
        ...
 } CamelStoreClass;

But these are not, and must be set by the implementation to something appropriate. By default a case-sensitive string hash and compare is used.

  • hash_folder_name: Should be set to a function which will properly hash folder names taking into account any specific rules for this store. e.g. IMAP folder names are case sensitive except for Inbox.
  • compare_folder_name: Similar to above.

Hooks

  • folder_opened: This is emitted any time a folder is opened. It is a convenient way for any client code needing to keep track of opened folders to find out about them. The event argument is the Evolution/Camel.Folder opened.

  • folder_created: Emitted when a new folder is created. The event argument is the CamelFolderInfo of the created folder (and any parents that needed to be created to create it).

  • folder_deleted: Emitted when a folder is deleted. The event argument is the CamelFolderInfo of the folder deleted.

 typedef struct _CamelRenameInfo {
        char *old_base;
        struct _CamelFolderInfo *new;
 } CamelRenameInfo;
  • folder_renamed: Emitted when a folder is renamed. The event argument is a CamelRenameInfo. old_base is the old location of the folder tree, and new is the new location, including any renamed subfolders.

  • folder_subscribed: Emitted when a folder is subscribed. The event argument is the CamelFolderInfo of the folder subscribed.

  • folder_unsubscribed: Emitted when a folder is unsubscribed. The event argument is the CamelFolderInfo of the folder subscribed.

Camel.FolderInfo

The CamelFolderInfo is an important core object which is used to comminicate lists and trees of folders between the store and client code.

One of the basic uses of the CamelFolderInfo structure is to display a list of folders to the user to choose from. As such, it includes enough information for a basic folder display, that can be used to present this list without having to open every folder present. The idea being that to open a new object for every folder would be time and memory consuming.

In practice, this doesn't really turn out to be that great an idea; the interfaces which create and use CamelFolderInfo's are some of the hardest to implement. Since only a subset of static information is provided, it isn't as flexible as it might be. And it just adds another layer of indirection required to access the Evolution/Camel.Folder objects themsevles.

 typedef struct _CamelFolderInfo {
        struct _CamelFolderInfo *next;
        struct _CamelFolderInfo *parent;
        struct _CamelFolderInfo *child;
 
        char *uri;
        char *name;
        char *full_name;
 
        guint32 flags;
        guint32 unread;
        guint32 total;
 } CamelFolderInfo;
  • next, parent, child: Link pointers for a tree of nodes
  • uri: A fully-qualified URI identifier which can be used to access this folder directly. Implementations must create this based on the Evolution/Camel.Service uri and the full folder name.

  • name: A printable representation of the folder name. This may be a translated string which is purely for display purposes, and may not have any relation to the physical folder name.
  • full_name: The full path-name to the folder in question. This must be in UTF-8 format, and have path components separated by '/' characters, with no leading '/'. For folder tree's, a subfolder's full_path must be a strict extension of the parent's full_path. That is, removing the last '/' and trailing path name MUST result in a full_name which matches the parent's full_name exactly. The full_name must be static across sessions (i.e. not translatable), and may or may not map directly to the implementation; if not, the implementation must provide it's own persistent mapping facility.
  • flags: Folder flags describing this folder. See the flags section below.
  • unread: The total number of unread messages in this folder, at the time the information was requested. If this store supports virtual junk or trash folders, then this count should not count any such messages.
  • total: The total number of messages in this folder. If this store supports virtual junk or trash folders, then the total count should not include junk or trash messages. In practice, this may be impossible to efficiently determine ... one reason why CamelFolderInfo isn't that grand an idea.

Then we have the flags. Note that these are all obviously, abstractions. They may not map DIRECTLY to a given implementation. They're not supposed to. Each implementation should just implement as much as it can, and choose appropriate defaults when it doesn't otherwise make sense.

  • CAMEL_FOLDER_NOSELECT: This folder cannot be opened as a normal folder. It is used for stub parent folders which cannot contain messages.
  • CAMEL_FOLDER_NOINFERIORS: This folder cannot have subfolders.
  • CAMEL_FOLDER_CHILDREN: This folder has children. The idea of this is that when a non-recursive get_folder_info is called, this will indicate if the folder has children or not. It should be set if it is unknown whether children exist or not.

  • CAMEL_FOLDER_NOCHILDREN: This folder definitely has no children. Basically opposite to above. This should not be set if unknown. This is somewhat redundant ... Infact both flags aren't implemented properly anyway

  • CAMEL_FOLDER_SUBSCRIBED: This is a subscribed folder. If the store supports subscriptions, this will be set on subscribed folders. It should always be set otherwise.
  • CAMEL_FOLDER_VIRTUAL: This folder is actually a virtual folder. Messages cannot be appended to virtual folders (this is to support UI validation).
  • CAMEL_FOLDER_SYSTEM: This is a system folder. System folders cannot be renamed or deleted. Implementations should set this flag on server-supplied folders which the user cannot remove, e.g. Inbox.
  • CAMEL_FOLDER_VTRASH: This is like a VIRTUAL folder but you can move messages to it. i.e. moving to a virtual trash folder deletes the message.
  • CAMEL_FOLDER_SHARED_TO_ME, CAMEL_FOLDER_SHARED_BY_ME: Accessing or sharing a shared folder. Just used by the UI to provide icons and plugin options.

Then there are additional bits which describe the basic folder type, in addition to the options above. They are just used to provide appropriate icons currently, although they could be used to help auto-configure things like Drafts and Sent folders with some more work.

  • CAMEL_FOLDER_TYPE_MASK: A mask used to get out the folder type information.
  • CAMEL_FOLDER_TYPE_NORMAL: A simple folder with nothing special about it.
  • CAMEL_FOLDER_TYPE_INBOX: An inbox folder, where new mail is normally delivered.
  • CAMEL_FOLDER_TYPE_OUTBOX: An outbox folder, where outgoing mail is spooled until sent outside the local system.
  • CAMEL_FOLDER_TYPE_TRASH: A rubbish folder. Holding deleted messages.
  • CAMEL_FOLDER_TYPE_JUNK: A junk folder. Holding messages marked as junk (spam).

Folder retrival functions

Now onto the methods in CamelStore. First, the folder retrival functions.

 #define CAMEL_STORE_FOLDER_CREATE (1<<0)
 #define CAMEL_STORE_FOLDER_EXCL (1<<1)
 #define CAMEL_STORE_FOLDER_BODY_INDEX (1<<2)
 #define CAMEL_STORE_FOLDER_PRIVATE (1<<3)
 
 CamelFolder *camel_store_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);

This is the basic folder opening function. folder_name corresponds to the full_name from the CamelFolderInfo structure.

The code entry point will manage the Evolution/Camel.Object#Camel.ObjectBag holding the folder names, and will this only ever call the virtual get_folder method on unopened folders. Implementation code therefore needn't worry about creating unique folder objects. A folder_opened event will automatically be created for any newly opened folders.

In the virtaul method, the implementation should create a Evolution/Camel.Folder corresponding to the folder and return it. Or set an exception in ex appropriately, and return NULL.

The flags are as follows:

  • CAMEL_STORE_FOLDER_CREATE: If the folder doesn't exist, then create it.
  • CAMEL_STORE_FOLDER_EXCL: If creating the folder, only create it if it doesn't exist.
  • CAMEL_STORE_FOLDER_BODY_INDEX: The folder should have it's content indexed.
  • CAMEL_STORE_FOLDER_PRIVATE: This is an internal folder which should not be seen by virtual folders. This is used internally.

The flags field is basically redundant now, only CREATE needs to be implemented. BODY_INDEX and other similar state information is handled by the Evolution/Camel.Object persistent state mechanisms now, and not by opening flags.

If the folder must be created, then a folder_created event must be emitted to broadcast this fact. In addition, if the store is running in a subscriptions mode, a folder_subscribed event must follow.

Then there are some helper functions to get specific folders. These are used by client code so they can access these folders directly, without having to scan CamelFolderInfo lists.

 CamelFolder *camel_store_get_inbox(CamelStore *store, CamelException *ex);
 CamelFolder *camel_store_get_trash(CamelStore *store, CamelException *ex);
 CamelFolder *camel_store_get_junk(CamelStore *store, CamelException *ex);

If a VTRASH or VJUNK folder is supported, then the entry points will just call camel_store_get_folder with a folder name of CAMEL_VTRASH_NAME and CAMEL_VJUNK_NAME respectively.

The default inbox name is inbox.

Folder manipulation functions

Then we have the folder manipulation functions. These work on folder name values, and not folder objects. In some cases, the entry point will manage the folders Evolution/Camel.Object#Camel.ObjectBag, and emit events as required. This should really be cleaned up to be consistent.

 CamelFolderInfo *camel_store_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);

Creates a folder under parent_name. This is (almost) identical to calling open_folder with a path name of parent_name/folder_name with the CREATE flag, except it does not open the folder object itself. A {{{parent_name{{{ of "" is used to create a top-level folder.

A CamelFolderInfo is retunred describing all folders that were created. This includes any parent folders that were created that didn't exist.

It is up to the implementation to emit a folder_created event. If the store is running in a subscription mode, then it should also subscribe the folder and follow with a folder_subscribed event.

The function will automatically prevent a clash with a junk or trash folder name if VJUNK or VTRASH is enabled for the store.

 void camel_store_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);

This will delete folder_name. The implementation should only allow empty folders with no subfolders to be deleted; but it is up to them. If the folder is deleted successfully and it is already open, the Evolution/Camel.Folder.folder_delete() method will be called on the open folder and it will be removed from the folders Evolution/Camel.Object#Camel.ObjectBag.

It is up to the implementation to emit a folder_deleted event for the deleted folder. It is also up to the implementation to unsubscribe from the folder and emit a folder_unsubscribed event if it is subscribed, before deleting it.

The function will automatically prevent a delete of a junk or trash folder name if VJUNK or VTRASH is enabled for the store.

 void camel_store_rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);

This will rename a folder to a new location. Any subfolders of old_name must also be renamed.

This function will automatically emit appropriate folder_renamed events. It does this by using get_folder_info after the folder is renamed to find out what folder tree exists under new_name.

The function will automatically prevent a clash with a junk or trash folder name if VJUNK or VTRASH is enabled for the store.

Folder listing functions

Now we come to the functions which list the folders on a store.

 #define CAMEL_STORE_FOLDER_INFO_FAST       (1 << 0)
 #define CAMEL_STORE_FOLDER_INFO_RECURSIVE  (1 << 1)
 #define CAMEL_STORE_FOLDER_INFO_SUBSCRIBED (1 << 2)
 #define CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL (1 << 3)
 
 CamelFolderInfo *camel_store_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex);

It is absolutely vital that any implementation of get_folder_info is correct and properly implements the interface. Failing to do so could result in the UI code wrongfully deleting whole folder trees. You have been warned!

This will list folders starting at top, depending on the supplied flags.

  • CAMEL_STORE_FOLDER_INFO_FAST: Only the folder listing is important, the total and unread counts should not be calculated if they are not readily available. On the other hand, if this is not set, then they should be calculated.

  • CAMEL_STORE_FOLDER_INFO_RECURSIVE: All folders under this folder should be listed, recursively. If this is not set, then only this folder and any immediately children should be listed. An implementation may always list the folders recursively, but must list them recursively if this is set.
  • CAMEL_STORE_FOLDER_INFO_SUBSCRIBED: Only list subscribed folders.
  • CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL: Don't include virtual Junk or Trash folders in the listing.

It is vitally important that any listing from top start exactly at top and then proceed to list any siblings and children from that point. This is because this interface is used by the client to query the folder lists when deleting and renaming folder trees.

 void camel_store_free_folder_info(CamelStore *store, CamelFolderInfo *fi);
 void camel_store_free_folder_info_full(CamelStore *store, CamelFolderInfo *fi);
 void camel_store_free_folder_info_nop(CamelStore *store, CamelFolderInfo *fi);

These are just helper function used to fill out the virtual methods in CamelStoreClass. Some implementations may store static information in various fields of CamelFolderInfo, etc. Only free_folder_info_full is very useful.

 void camel_folder_info_free(CamelFolderInfo *fi);
 CamelFolderInfo *camel_folder_info_clone(CamelFolderInfo *fi);

These functions work on 'full' CamelFolderInfos. That is, where all strings are allocated using g_malloc.

 CamelFolderInfo *camel_folder_info_build(GPtrArray *folders, const char *namespace, char separator, gboolean short_names);

This function can be used to convert a flag array of CamelFolderInfo pointers into a tree structure. Do not use this in new code, do it yourself.

Subscription functions

CamelStore interfaces manage subscriptions.

First, an accessor which just checks the SUBSCRIPTIONS flag:

 gboolean camel_store_supports_subscriptions(CamelStore *store);

Then a query function for a specific folder name:

 gboolean camel_store_folder_subscribed(CamelStore *store, const char *folder_name);

And control functions to manage them:

 void camel_store_subscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex);
 void camel_store_unsubscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex);

It is up to each implementation to provide appropriate folder_subscribed and folder_unsubscribed events.

Control &amp; other functions

All folders in a store may be synchronised to the backing store using a convenience function:

 void camel_store_sync(CamelStore *store, int expunge, CamelException *ex);

This includes an optional expunge. Note that the default implementation will only expunge open folders.

The noop function is a mighty hack, to get around the problem of providing a timeout callback to a synchronous api. It may be called periodically to ping the server to keep a connection alive.

 void camel_store_noop(CamelStore *store, CamelException *ex);

folder_uri_equal is a function that can compare two URI's to see if they refer to the same physical folder. You must use this, rather than say strcmp since it takes into account various per-provider rules for comparing store URI's and folder names. It uses the store virtual method compare_folder_name to implement it.

 int camel_store_folder_uri_equal(CamelStore *store, const char *uri0,  const char *uri1);

Notes

camel_store_get_folder_info is hard to implement and hard to use. It should probably be changed to take a file-globbing like pattern instead, and take no flags. It should also be changed to return a simple flat-list of matching folders, rather than a tree.

The whole CamelFolderInfo thing could probably be removed. Instead, just return Evolution/Camel.Folder objects directly. If this was done, then there would also be no requirement for most of the folder manipulation functions; they could just be virtual methods on Evolution/Camel.Folder, which would now contain folders or messages rather than just messages.

CAMEL_STORE_FOLDER_INFO_FAST is messy. It should always be fast. Infact in Evolution 2.4 this isn't really used anymore - Evolution/Camel.Folder.refresh_info() is used instead to update folder counts.

Locking??

Apps/Evolution/Camel.Store (last edited 2013-08-08 22:50:04 by WilliamJonMcCann)