Camel.Folder

CamelFolder is the object used to abstract a mail folder that stores messages. Folders are obtained from Evolution/Camel.Stores and then used to:

  • Retrieve information about messages in the folder
  • Search for messages
  • Retrieve message content
  • Copy and move messages
  • Add new messages

The abstraction used maps closely to IMAP's folder abstraction, the Berkeley Mailbox format, and Maildir, to name a few. Basically the folder is an ordered store of messages, ordered by arrival time.

  • Messages can only be appended to the mailbox, and cannot be altered once they are created, without appending a new message and deleting the old one.
  • Messages are identified by a 'UID' - a persistent unique identifier, which is a simple string.
  • Messages have some modifyable state, 'flags'.
  • Messages are deleted by marking them as deleted, then expunging the folder.

CamelFolder is really a collection of classes which are required to implement all of this complex functionality:

Base class

 struct _CamelFolder {
        CamelObject parent_object;
 
        struct _CamelFolderPrivate *priv;
 
        char *name;
        char *full_name;
        char *description;
 
        CamelStore *parent_store;
        CamelFolderSummary *summary;
 
        guint32 folder_flags;
        guint32 permanent_flags;
 };

 void camel_folder_construct(CamelFolder *folder, CamelStore *parent_store, const char *full_name, const char *name);

Most of the above fields are for implementors to use or are internal. description is used for example by CamelFolder to store a static copy of the CAMEL_OBJECT_DESCRIPTION core property.

The folder_flags field is for initialisation by the implementation. By default it is unset.

CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY

The summary object exists, and all of the summary-related functions (state information, CamelMessageInfo) are implemented.

CAMEL_FOLDER_HAS_SEARCH_CAPABILITY
The search related functions are implemented.
CAMEL_FOLDER_FILTER_RECENT

Any RECENT messages will be passed through the incoming filter from the Evolution/Camel.Session.

CAMEL_FOLDER_HAS_BEEN_DELETED
Used to track of a live object now points to a deleted folder.
CAMEL_FOLDER_IS_TRASH
This is a rubbish folder. Used for non-vtrash based implementations.
CAMEL_FOLDER_IS_JUNK
This is a folder containing junk messages.
CAMEL_FOLDER_FILTER_JUNK
Indicates the folder will be checked for junk messages automatically. This works at the same point as the FILTER_RECENT flag.

All implementors must call construct when they setup their folder, it will initialise some of the fields dependent on the parent store or names.

Note that a CamelFolder MUST NOT own a reference to its parent store. It is up to the client code never to access folders from a store from which it doesn't own a reference.

Then we have some of the basic folder operations.

 void camel_folder_refresh_info(CamelFolder *folder,  CamelException *ex);

Refresh info should check the folder for any new or modified mail. Whether this needs to do much work depends entirely on the backend, some may always be up to date. With IMAP this generally means the folder must be SELECTed, and new messages checked.

 void camel_folder_sync(CamelFolder *folder,  gboolean expunge,  CamelException *ex);
 void camel_folder_expunge(CamelFolder *folder,  CamelException *ex);

These will force a synchronisation from the client to the server. It is in effect the complement to refresh_info. Again, some backends may not need an explicit sync, or they may implement it asynchronously; it is up to them.

Note that expunge is deprecated in favour of calling sync with expunge = TRUE.

 void camel_folder_append_message(CamelFolder *folder,  CamelMimeMessage *message, const CamelMessageInfo *info, char **appended_uid, CamelException *ex);

This is the interface for adding new messages to a folder. The supplied info should be used to setup the state of the new message if possible. It may not always be possible, depending on the backend, at least to store all of the user state stored in the CamelMessageInfo; it should be treated as best-effort. appended_uid can be used to return the UID of the newly appended message. But since this feature is impossible to implement with some backends, it cannot be relied upon in client code; its utility is highly questionable.

 void camel_folder_transfer_messages_to(CamelFolder *source, GPtrArray *uids, CamelFolder *dest, GPtrArray **transferred_uids, gboolean delete_originals, CamelException *ex);

Another way to add messages to a folder is by moving them around. This interface may be far more efficient if the messages are being moved around on the same server or backend. Otherwise a fallback implementation that just uses append_message is provided automatically. Only override this method if you can and want to improve the efficiency.

 CamelMimeMessage * camel_folder_get_message(CamelFolder *folder,  const char *uid,  CamelException *ex);

And finally the meat - retrieving message content. An implementation may simply use the Evolution/Camel.DataWrapper.construct_from_stream() and related methods to build entire objects in memory. Or it can subclass messages or streams or datawrappers to create complex on-demand objects backed by remote or local non-memory resources. It just has to appear the same through the abstracted APIs.

Hooks

Note that like other Evolution/Camel.Objects, events may come from any thread at any time.

; folder_changed : This event will be emitted whenever some property of the messages within the folder changes. The event data is a Evolution/#Camel.FolderChangeInfo structure. It can be used to track the state of the folder without having to poll the entire list of messages regularly, being the driver behind a view-model-controller message list display. This event may be blocked by the freeze function, in which case any subsequent changes are batched up until a maching thaw is called. ; deleted : Indicates that the folder was deleted. The event data is not used. This signal is often more convenient than using the corresponding Evolution/Camel.Store folder_deleted event. ; renamed : Indicates that the folder was renamed. The event data is a string containing the old full_name of the folder. This is more convenient than using the corresponding Evolution/Camel.Store folder_renamed event.

Properties

CamelFolder has a large number of properties. These should be used rather than the accessor methods where appropriate.

; CAMEL_FOLDER_NAME : The display name of this folder. This is the same as the Evolution/Camel.Store#Camel.FolderInfo.name. ; CAMEL_FOLDER_FULL_NAME : The full path name of this folder. This is the same as the Evolution/Camel.Store#Camel.FolderInfo.full_name. ; CAMEL_FOLDER_STORE : The store this folder belongs to. All folders must belong to a store. It is safe to dereference the parent_store pointer as well. ; CAMEL_FOLDER_PERMANENTFLAGS : The permanent flags for this folder. These flags will be permanently stored between sessions and between computers. Note that generally all flags will be session-persistent, but only these will be stored on a server. ; CAMEL_FOLDER_TOTAL : The total number of messages in the folder. ; CAMEL_FOLDER_UNREAD : The total number of unread messages in the folder. ; CAMEL_FOLDER_DELETED : The total number of deleted messages in the folder. ; CAMEL_FOLDER_JUNKED : The total number of junk messages in the folder. ; CAMEL_FOLDER_VISIBLE : The total number of visible messages - that is messages not junk or deleted. ; CAMEL_FOLDER_UID_ARRAY : Get all UIDs in the folder in a single array. ; CAMEL_FOLDER_INFO_ARRAY : Get all of the Evolution/#Camel.MessageInfo in a single array. ; CAMEL_FOLDER_PROPERTIES : Retrieve a GSList of Evolution/Camel.Object#Camel.Property descriptors of all user-settable persistent properties. This can be used to dynamically build user interface elements for property windows on folders.

There are also a number of matching property accessor functions. These are all deprecated. The primary reason is API overhead, and another good reason is atomicicity and other thread related problems.

 guint32 camel_folder_get_permanent_flags(CamelFolder *folder);
 
 const char *camel_folder_get_name(CamelFolder *folder);
 const char *camel_folder_get_full_name(CamelFolder *folder);
 
 CamelStore *camel_folder_get_parent_store(CamelFolder *folder);
 
 int camel_folder_get_message_count(CamelFolder *folder);
 int camel_folder_get_unread_message_count(CamelFolder *folder);
 int camel_folder_get_deleted_message_count(CamelFolder *folder);

This is the CAMEL_FOLDER_INFO_ARRAY accessor:

 GPtrArray *camel_folder_get_summary(CamelFolder *folder);
 void camel_folder_free_summary(CamelFolder *folder, GPtrArray *array);

And the CAMEL_FOLDER_UID_ARRAY accessor:

 GPtrArray * camel_folder_get_uids(CamelFolder *folder);
 void camel_folder_free_uids(CamelFolder *folder, GPtrArray *array);

Note that the _ARRAY accessors are removed in the disk summary branch implementations as they are not incremental.

Folder changes

When a folder is changed through various operations - adding new messages, changing message status, etc, a folder_changed event is used to communicate this to client code. It contains details about which messages changed and in what way they changed. If used correctly this is a simple and efficient way to update a display or other information about a folder without having to poll the whole folder every time.

 struct _CamelFolderChangeInfo {
        GPtrArray *uid_added;
        GPtrArray *uid_removed;
        GPtrArray *uid_changed;
        GPtrArray *uid_recent;
 
        struct _CamelFolderChangeInfoPrivate *priv;
 };

There are 4 basic types of change:

  • Added UIDs - new messages copied or appended to the folder
  • Removed UIDs - messages which have been permanently expunged from the folder (not normally the same as deleting)
  • Changed UIDs - messages whose state has been modified

  • Recent UIDs - new messages appended to the folder which have not been seen before - usually the client never sees this

There is a subtle difference between added and recent UIDs. Added just mean they have been added to the folder list at some point after the folder is opened. This may just mean that the folder was not physically scanned at the time the folder was opened, and so the messages are not recent as such.

Also note that a UID will never appear in more than one of the added, removed or changed lists. This is enforced by the CamelFolderChangeInfo manipulation functions. i.e. if a UID is added, then removed, it is simply treated as removed (there is no guarantee client code didn't find the uid by other means after it was added, so the removed event needs to be kept.)

There are a bunch of functions used by implementation code to create an manipulate the CamelFolderChangeInfo data; see the source/api documentation for camel_folder_change_info_* functions.

Backend code should try to batch up as many changes as it can at once; either by using freeze or just by using its own CamelFolderChangeInfo.

Some of these events are automatically created by the base class, others are not. The disk summary branch implementation clears this up a lot and makes it more consistent.

For efficiency reasons, a client (or backend) may stop emission of folder changed events using freeze and re-start them using thaw. These functions nest.

 void camel_folder_freeze(CamelFolder *folder);
 void camel_folder_thaw(CamelFolder *folder);
 gboolean camel_folder_is_frozen(CamelFolder *folder);

When frozen, no folder_changed events will be emitted, and all changes created by the backend code will be merged into a single event later on automatically.

Message state

Message state can be set and retrieved using the Evolution/Camel.FolderSummary#Camel.MessageInfo structure.

The main folder interface is get_message_info which is used to retrieve the information about a message based on the message's UID. It simply returns NULL if the message doesn't exist.

 CamelMessageInfo *camel_folder_get_message_info(CamelFolder *folder, const char *uid);

CamelMessageInfos are refcounted, these are the old deprecated interfaces; do not use these anymore. See Evolution/Camel.FolderSummary#Camel.MessageInfo instead.

 void camel_folder_free_message_info(CamelFolder *folder, CamelMessageInfo *info);
 void camel_folder_ref_message_info(CamelFolder *folder, CamelMessageInfo *info);

There are also folder-specific duplicates of all of the state-related functions. Do not use these, they are all deprecated.

 guint32 camel_folder_get_message_flags(CamelFolder *folder, const char *uid);
 gboolean camel_folder_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
 gboolean camel_folder_get_message_user_flag(CamelFolder *folder, const char *uid, const char *name);
 void camel_folder_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value);
 const char * camel_folder_get_message_user_tag(CamelFolder *folder, const char *uid, const char *name);
 void camel_folder_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value);
 
 #define camel_folder_delete_message(folder, uid) \ camel_folder_set_message_flags(folder, uid, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN)

Unless your implementation needs to know about state changes (for example to implement a non-virtual trash or junk folder), then it doesn't need to override any of these functions, they are provided by the base class.

Also note that a folder needn't implement this facility either, for simple backends like POP3 for example, in which case it must override this function and return FALSE:

 gboolean camel_folder_has_summary_capability(CamelFolder *folder);

This is also deprecated - in the future all folders will need to support a summary.

Searching

Searching is one of the most important and useful features of CamelFolder. Searches are driven by a simple expression evaluator based on s-expressions. The search details are defined in Evolution/Camel.FolderSearch, which implements the actual search mechanism. Currently, implementations must override all of these virtual methods to implement their search.

 gboolean camel_folder_has_search_capability(CamelFolder *folder);
 GPtrArray * camel_folder_search_by_expression(CamelFolder *folder, const char *expr, CamelException *ex);
 GPtrArray * camel_folder_search_by_uids(CamelFolder *folder, const char *expr, GPtrArray *uids, CamelException *ex);
 void camel_folder_search_free(CamelFolder *folder, GPtrArray *result);

Note that the disksummary branch has a signficantly different search interface. It isn't optional, and normally the virtual methods do not need overriding as the base class implements it (through the Evolution/CamelDS.CamelFolderSummary object.

Notes

This object is very over-engineered. There are too many options and too many ways of doing the same thing.

Because CamelMessageInfo arrays are passed around for all messages in a folder, scalability is a major issue. It is the single biggest memory user of evolution mail. The disksummary branch was designed to fix these problems by changing the api's to be more incremental and database-friendly.

Although CamelFolder supports a delete + expunge model for removing messages, it isn't enforced by the API. This allows backends which require a 'Deleted Items' folder to be implemented easily, they just need to make sure the abstraction provided by the API is honoured. This can be achieved quite easily by snooping the flag changes, and if a message is deleted, move it to the new folder and emit a folder_changed event with the message in question 'removed'.

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