/* ** Copyright 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #ifndef maildirkeywords_h #define maildirkeywords_h #include "config.h" #include #include #ifdef __cplusplus extern "C" { #endif /* ** IMAP keywords. This data structure is designed so that it is possible to: ** A) Find all messages that have the given keyword, ** B) Find all keywords that are set for the given message, ** C) Optimize memory usage, even with many keywords set for many msgs. */ /* A doubly-linked list of keywords */ struct libmail_keywordEntry { struct libmail_keywordEntry *prev, *next; /* Doubly-linked list */ #define keywordName(ke) ((char *)((ke)+1)) union { void *userPtr; /* Misc ptr */ unsigned long userNum; /* Or misc num */ } u; struct libmail_kwMessageEntry *firstMsg, *lastMsg; /* Doubly-linked list of messages that use this keyword */ }; /* The main keyword hash table */ struct libmail_kwHashtable { struct libmail_keywordEntry heads[43], tails[43]; /* Dummy head/tail nodes for each hash bucket */ int keywordAddedRemoved; }; struct libmail_kwMessageEntry { struct libmail_kwMessageEntry *next, *prev; /* Doubly-linked list of all keywords set for this message */ struct libmail_kwMessageEntry *keywordNext, *keywordPrev; /* Doubly-linked list of all entries for the same keyword */ struct libmail_keywordEntry *libmail_keywordEntryPtr; /* The keyword */ struct libmail_kwMessage *libmail_kwMessagePtr; /* The message */ }; struct libmail_kwMessage { struct libmail_kwMessageEntry *firstEntry, *lastEntry; /* Doubly-linked list of all keywords set for this message */ union { void *userPtr; /* Misc ptr */ unsigned long userNum; /* Or misc num */ } u; }; /* ** Initialize a libmail_kwHashtable */ void libmail_kwhInit(struct libmail_kwHashtable *); /* ** Returns 0 if libmail_kwHashtable is empty. Return -1, errno=EIO if it's ** not (sanity check). */ int libmail_kwhCheck(struct libmail_kwHashtable *); /* ** Enumerate all defined keywords. */ int libmail_kwEnumerate(struct libmail_kwHashtable *h, int (*callback_func)(struct libmail_keywordEntry *, void *), void *callback_arg); /* ** Find a keyword in the hashtable, optionally create it. If createIfNew, ** then we MUST add the returned result to some keywordMessage, else there'll ** be a cleanup problem. */ struct libmail_keywordEntry * libmail_kweFind(struct libmail_kwHashtable *ht, const char *name, int createIfNew); extern const char *libmail_kwVerbotten; /* ** Optional list of chars prohibited in keyword names. They get automagically ** replaced with underscores. */ extern int libmail_kwCaseSensitive; /* ** Non zero if keyword names are case sensitive. */ /* ** Clear a reference to a particular keyword, from a particular message. */ void libmail_kweClear(struct libmail_keywordEntry *); /* ** Create an abstract message object, with no keywords currently defined. */ struct libmail_kwMessage *libmail_kwmCreate(); /* ** Destroy the message object, automatically removing any keywords that were ** used by the object. */ void libmail_kwmDestroy(struct libmail_kwMessage *); /* ** Link a keyword to a message. */ int libmail_kwmSet(struct libmail_kwMessage *, struct libmail_keywordEntry *); /* ** Link a keyword to a message, by keyword's name. */ int libmail_kwmSetName(struct libmail_kwHashtable *, struct libmail_kwMessage *, const char *); /* ** Compare two messages, return 0 if they have the same keywords. */ int libmail_kwmCmp(struct libmail_kwMessage *, struct libmail_kwMessage *); /* ** Clear a keyword from a message. */ int libmail_kwmClearName(struct libmail_kwMessage *, const char *); /* ** libmail_kwmClearName is for INTERNAL USE ONLY -- the name doesn't get vetted ** by libmail_kweFind. */ /* ** Clear a keyword from a message, the public version. */ int libmail_kwmClear(struct libmail_kwMessage *, struct libmail_keywordEntry *); /* ** */ int libmail_kwmClearEntry(struct libmail_kwMessageEntry *); /***************************************************************************** The above is low-level stuff. And now, a high level maildir storage API: *****************************************************************************/ /* ** Read keywords from a maildir. The application presumably has read and ** compiled a list of messages it found in the maildir's cur (and new) ** directories. ** ** The function maildir_kwRead() will now read the keywords associated with ** each message. How the application maintains the list of messages in the ** maildir is irrelevant. The application simply needs to allocate a pointer ** to a libmail_kwMessage structure, one pointer for each message in the ** maildir. Each pointer must be initialized to a NULL, and the application ** provides a set of callback functions, as defined below, that return ** a pointer to this pointer (pay attention now), given the filename. ** maildir_kwRead() invokes the callback functions, as appropriate, while ** it's doing its business. ** ** There's other callback functions too, so let's get to business. ** The application must initialize the following structure before calling ** maildir_kwRead(). This is where the pointers to all callback functions ** are provided: */ struct maildir_kwReadInfo { struct libmail_kwMessage **(*findMessageByFilename)(const char *filename, int autocreate, size_t *indexNum, void *voidarg); /* ** Return a pointer to a libmail_kwMessage * that's associated with ** the message whose name is 'filename'. 'filename' will not have ** :2, and the message's flags, so the application needs to be ** careful. ** ** All libmail_kwMessage *s are initially NULL. If autocreate is not ** zero, the application must use libmail_kwmCreate() to initialize ** the pointer, before returning. Otherwise, the application should ** return a pointer to a NULL libmail_kwMessage *. ** ** The application may use libmail_kwMessage.u for its own purposes. ** ** The application should return NULL if it can't find 'filename' ** in its list of messages in the maildir. That is a defined ** possibility, and occur in certain race conditions (which are ** properly handled, of course). ** ** If indexNum is not NULL, the application should set *indexNum to ** the found message's index (if the application does not return NULL). ** All messages the application has must be consecutively numbered, ** beginning with 0 and up to, but not including, whatever the ** application returns in getMessageCount(). */ size_t (*getMessageCount)(void *voidarg); /* ** The application should return the number of messages it thinks ** there are in the maildir. */ struct libmail_kwMessage **(*findMessageByIndex)(size_t indexNum, int autocreate, void *voidarg); /* ** This one is like the findMessageByFilename() callback, except that ** instead of filename the applicationg gets indexNum which ranges ** between 0 and getMessageCount()-1. ** The return code from this callback is identical to the return code ** from findMessageByFilename(), and autocreate's semantics are also ** the same. */ const char *(*getMessageFilename)(size_t n, void *voidarg); /* ** The application should return the filename for message #n. The ** application may or may not include :2, in the returned ptr. */ struct libmail_kwHashtable * (*getKeywordHashtable)(void *voidarg); /* ** The application should return the libmail_kwHashtable that it ** allocated to store all the keyword stuff. Read keywords are ** allocated from this hashtable. */ void (*updateKeywords)(size_t n, struct libmail_kwMessage *kw, void *voidarg); /* ** The updateKeywords callback gets invoked by maildir_kwRead() ** if it needs to throw out the list of keywords it already read for ** a given message, and replace it, instead, with another set of ** keywords. This can happen in certain circumstances. ** ** The application should retrieve the libmail_kwMessage pointer for ** message #n. It may or may not be null. If it's not null, the ** application must use libmail_kwmDestroy(). Then, afterwards, ** the application should save 'kw' as the new pointer. ** ** This callback is provided so that the application may save whatever ** it wants to save in kw->u.userPtr or kw->u.userNum, because 'kw' ** was created by libmail_kwRead(), and not one of the two findMessage ** callbacks. */ void *voidarg; /* ** All of the above callbacks receive this voidarg as their last ** argument. */ int tryagain; /* ** libmail_kwRead() returns 0 for success, or -1 for a system failure ** (check errno). ** ** If libmail_kwRead() returned 0, the application MUST check ** tryagain. ** ** If tryagain is not 0, the application MUST: ** A) Take any non-NULL libmail_kwMessage pointers that are ** associated with each message in the maildir, use ** libmail_kwmDestroy() to blow them away, and reset each ** pointer to NULL. ** ** B) Invoke libmail_kwRead() again. ** ** A non-0 tryagain indicates a recoverable race condition. */ /* Everything below is internal */ int updateNeeded; int errorOccured; }; int maildir_kwRead(const char *maildir, struct maildir_kwReadInfo *rki); /* ** maildir_kwSave saves new keywords for a particular message: */ int maildir_kwSave(const char *maildir, /* The maildir */ const char *filename, /* The message. :2, is allowed, and ignored */ struct libmail_kwMessage *newKeyword, /* New keywords. The ptr may be NULL */ char **tmpname, char **newname, int tryAtomic); int maildir_kwSaveArray(const char *maildir, const char *filename, const char **flags, char **tmpname, char **newname, int tryAtomic); /* ** maildir_kwSave returns -1 for an error. If it return 0, it will initialize ** *tmpname and *newname, both will be full path filenames. The application ** needs to simply call rename() with both filenames, and free() them, to ** effect the change. Example: ** ** char *tmpname, *newname; ** ** if (maildir_kwSave( ..., &tmpname, &newname) == 0) ** { ** rename(tmpname, newname); ** ** free(tmpname); ** free(newname); ** } ** ** Of course, rename()s return code should also be checked. ** ** If 'tryAtomic' is non-zero, the semantics are going to be slightly ** different. tryAtomic is non-zero when we want to update keywords ** atomically. To do that, first, use maildir_kwRead() (or, most likely ** maildir_kwgReadMaildir) to read the existing keywords, update the keywords ** for the particular message, use maildir_kwSave(), but instead of rename() ** use link(). Whether link succeeds or not, use unlink(tmpname) in any ** case. If link() failed with EEXIST, we had a race condition, so try ** again. ** Note - in NFS environments, must check not only that links succeeds, but ** if stat-ing the tmpfile the number of links also must be 2. */ /* ** Set libmail_kwEnabled to ZERO in order to silently disable all maildir ** keyword functionality. It's optional in Courier-IMAP. Setting this flag ** to zero disables all actual keyword read/write functions, however all the ** necessary data still gets created (namely the courierimapkeywords ** subdirectory. */ extern int libmail_kwEnabled; /* ** The following functions are "semi-internal". ** ** maildir_kwExport() uses the same struct maildir_kwReadInfo, to "export" ** the list of keywords assigned to all messages into a file. ** ** maildir_kwImport() imports the saved keyword list. ** ** These functions are meant to save a "snapshot" of the keywords into a ** flag file, nothing else. */ int maildir_kwExport(FILE *, struct maildir_kwReadInfo *); int maildir_kwImport(FILE *, struct maildir_kwReadInfo *); /**************************************************************************** Generic maildir_kwRead implementation. ****************************************************************************/ struct libmail_kwGeneric { struct libmail_kwHashtable kwHashTable; size_t nMessages; struct libmail_kwGenericEntry **messages; int messagesValid; struct libmail_kwGenericEntry *messageHashTable[99]; }; struct libmail_kwGenericEntry { struct libmail_kwGenericEntry *next; /* On the hash table */ char *filename; size_t messageNum; struct libmail_kwMessage *keywords; }; void libmail_kwgInit(struct libmail_kwGeneric *g); int libmail_kwgDestroy(struct libmail_kwGeneric *g); int libmail_kwgReadMaildir(struct libmail_kwGeneric *g, const char *maildir); struct libmail_kwGenericEntry * libmail_kwgFindByName(struct libmail_kwGeneric *g, const char *filename); struct libmail_kwGenericEntry * libmail_kwgFindByIndex(struct libmail_kwGeneric *g, size_t n); #ifdef __cplusplus } #include #include /* Some C++ wrappers */ namespace mail { namespace keywords { class Hashtable { public: struct libmail_kwHashtable kwh; Hashtable(); ~Hashtable(); Hashtable(const Hashtable &); /* UNDEFINED */ Hashtable &operator=(const Hashtable &); /* UNDEFINED */ }; class MessageBase { public: struct libmail_kwMessage *km; size_t refCnt; MessageBase(); ~MessageBase(); MessageBase(const MessageBase &); /* UNDEFINED */ MessageBase &operator=(const MessageBase *); /* UNDEFINED */ }; class Message { MessageBase *b; bool copyOnWrite(); public: Message(); ~Message(); Message(const Message &); Message &operator=(const Message &); void getFlags(std::set &) const; /* Extract list of flags */ bool setFlags(Hashtable &, const std::set &); /* Set the flags. */ bool addFlag(Hashtable &, std::string); bool remFlag(std::string); bool empty() const { return b->km == NULL || b->km->firstEntry == NULL; } bool operator==(struct libmail_kwMessage *m) const { return b->km == NULL ? m == NULL || m->firstEntry == NULL: m && libmail_kwmCmp(b->km, m) == 0; } bool operator !=(struct libmail_kwMessage *m) const { return ! operator==(m); } void replace(struct libmail_kwMessage *p) { if (b->km) libmail_kwmDestroy(b->km); b->km=p; } }; } } /* BONUS: */ int maildir_kwSave(const char *maildir, const char *filename, std::set keywords, char **tmpname, char **newname, int tryAtomic); #endif #endif