summaryrefslogtreecommitdiffstats
path: root/libmail/maildir.C
diff options
context:
space:
mode:
Diffstat (limited to 'libmail/maildir.C')
-rw-r--r--libmail/maildir.C1718
1 files changed, 1718 insertions, 0 deletions
diff --git a/libmail/maildir.C b/libmail/maildir.C
new file mode 100644
index 0000000..4bfc908
--- /dev/null
+++ b/libmail/maildir.C
@@ -0,0 +1,1718 @@
+/*
+** Copyright 2002-2004, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+#include "libmail_config.h"
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "expungelist.H"
+#include "misc.H"
+#include "maildir.H"
+#include "driver.H"
+#include "search.H"
+#include "copymessage.H"
+#include "file.H"
+
+#include "maildir/config.h"
+#include "maildir/maildirmisc.h"
+#include "maildir/maildirquota.h"
+#include "rfc2045/rfc2045.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <list>
+#include <map>
+
+using namespace std;
+
+/////////////////////////////////////////////////////////////////////////////
+
+LIBMAIL_START
+
+static bool open_maildir(mail::account *&accountRet,
+ mail::account::openInfo &oi,
+ mail::callback &callback,
+ mail::callback::disconnect &disconnectCallback)
+{
+ if (oi.url.substr(0, 8) != "maildir:")
+ return false;
+
+ accountRet=new mail::maildir(disconnectCallback,
+ callback, oi.url.substr(8));
+ return true;
+}
+
+static bool maildir_remote(string url, bool &flag)
+{
+ if (url.substr(0, 8) != "maildir:")
+ return false;
+
+ flag=false;
+ return true;
+}
+
+driver maildir_driver= { &open_maildir, &maildir_remote };
+
+LIBMAIL_END
+
+/////////////////////////////////////////////////////////////////////////////
+
+mail::maildir::maildirMessageInfo::maildirMessageInfo()
+ : lastKnownFilename(""), changed(false)
+{
+}
+
+mail::maildir::maildirMessageInfo::~maildirMessageInfo()
+{
+}
+
+#define CONSTRUCTOR \
+ calledDisconnected(false), \
+ sameServerFolderPtr(NULL), \
+ folderCallback(NULL), \
+ cacheRfcp(NULL), cachefd(-1), \
+ watchFolder(NULL), lockFolder(NULL), watchStarting(false)
+
+mail::maildir::maildir(mail::callback::disconnect &disconnect_callback,
+ mail::callback &callback,
+ string pathArg)
+ : mail::account(disconnect_callback), CONSTRUCTOR
+{
+ if (init(callback, pathArg))
+ callback.success("Mail folders opened");
+}
+
+mail::maildir::maildir(mail::callback::disconnect &disconnect_callback)
+ : mail::account(disconnect_callback), CONSTRUCTOR
+{
+ // Used by pop3maildrop subclass.
+}
+
+bool mail::maildir::init(mail::callback &callback,
+ string pathArg)
+{
+ ispop3maildrop=false;
+
+ if (pathArg.size() == 0)
+ pathArg="Maildir";
+
+ string h=mail::homedir();
+
+ if (h.size() == 0)
+ {
+ callback.fail("Cannot find my home directory!");
+ return false;
+ }
+
+ if (pathArg[0] != '/')
+ pathArg= h + "/" + pathArg;
+
+ static const char * const subdirs[]={"tmp","cur","new", 0};
+
+ int i;
+
+ for (i=0; subdirs[i]; i++)
+ {
+ struct stat stat_buf;
+
+ string s= pathArg + "/" + subdirs[i] + "/.";
+
+ if (stat(s.c_str(), &stat_buf) < 0)
+ {
+ s="Cannot open maildirAccount: ";
+ s += strerror(errno);
+ callback.fail(s);
+ return false;
+ }
+ }
+
+ path=pathArg;
+ return true;
+}
+
+mail::maildir::~maildir()
+{
+ updateNotify(false);
+
+ if (lockFolder)
+ maildirwatch_free(lockFolder);
+ if (cacheRfcp)
+ rfc2045_free(cacheRfcp);
+ if (cachefd >= 0)
+ close(cachefd);
+ if (!calledDisconnected)
+ {
+ calledDisconnected=true;
+ disconnect_callback.disconnected("");
+ }
+
+ index.clear();
+}
+
+void mail::maildir::resumed()
+{
+}
+
+void mail::maildir::handler(vector<pollfd> &fds, int &ioTimeout)
+{
+ int fd;
+ int rc;
+ int changed, timeout;
+
+ if (!watchFolder)
+ return;
+
+ if (watchStarting)
+ {
+ rc=maildirwatch_started(&watchFolderContents, &fd);
+
+ if (rc < 0)
+ {
+ updateNotify(false);
+ return;
+ }
+ if (rc > 0)
+ {
+ watchStarting=false;
+ updateFolderIndexInfo(NULL, false);
+ }
+ if (fd < 0)
+ return;
+
+ pollfd pfd;
+
+ pfd.fd=fd;
+ pfd.events=pollread;
+ pfd.revents=0;
+ fds.push_back(pfd);
+ return;
+ }
+
+ if (maildirwatch_check(&watchFolderContents, &changed, &fd, &timeout)
+ < 0)
+ {
+ updateNotify(false);
+ return;
+ }
+ if (changed)
+ {
+ ioTimeout=0;
+
+ MONITOR(mail::maildir);
+
+ updateNotify(false);
+ updateFolderIndexInfo(NULL, false);
+
+ if (!DESTROYED())
+ updateNotify(true);
+ updateFolderIndexInfo(NULL, false);
+ return;
+ }
+
+ timeout *= 1000;
+
+ if (timeout < ioTimeout)
+ {
+ ioTimeout=timeout;
+ }
+ if (fd < 0)
+ return;
+
+ pollfd pfd;
+
+ pfd.fd=fd;
+ pfd.events=pollread;
+ pfd.revents=0;
+ fds.push_back(pfd);
+}
+
+void mail::maildir::logout(mail::callback &callback)
+{
+ updateNotify(false);
+
+ if (!calledDisconnected)
+ {
+ calledDisconnected=true;
+ disconnect_callback.disconnected("");
+ }
+ callback.success("OK");
+}
+
+//
+// Callback collects filenames moved from new to cur on this maildirAccount scan
+//
+static void recent_callback_func(const char *c, void *vp)
+{
+ set<string> *setPtr=(set<string> *)vp;
+
+ try {
+ setPtr->insert( string(c));
+ } catch (...) {
+ }
+}
+
+// Update mail::messageInfo with the current message flags
+// (read from the message's filename)
+// Returns true if flags have been modified
+
+bool mail::maildir::updateFlags(const char *filename,
+ mail::messageInfo &info)
+{
+ bool flag;
+ bool changed=false;
+
+#define DOFLAG(name, NOT, theChar) \
+ if ((flag= NOT !!maildir_hasflag(filename, theChar)) != info.name) \
+ { info.name=flag; changed=true; }
+#define DOFLAG_EMPTY
+
+ DOFLAG(draft, DOFLAG_EMPTY, 'D');
+ DOFLAG(replied, DOFLAG_EMPTY, 'R');
+ DOFLAG(marked, DOFLAG_EMPTY, 'F');
+ DOFLAG(deleted, DOFLAG_EMPTY, 'T');
+ DOFLAG(unread, !, 'S');
+
+#undef DOFLAG
+#undef DOFLAG_EMPTY
+
+ return changed;
+}
+
+//
+// Get the filename for message #n
+//
+
+string mail::maildir::getfilename(size_t i)
+{
+ string n("");
+
+ if (i >= index.size())
+ return n;
+
+ char *fn;
+
+ char *dir=maildir_name2dir(path.c_str(), folderPath.c_str());
+
+ if (!dir)
+ return n;
+
+ try {
+ fn=maildir_filename(dir, NULL,
+ index[i].lastKnownFilename.c_str());
+ free(dir);
+ } catch (...) {
+ free(dir);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+
+ if (fn)
+ try {
+ n=fn;
+ free(fn);
+ } catch (...) {
+ free(fn);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+
+ return n;
+}
+
+void mail::maildir::checkNewMail(callback &callback)
+{
+ checkNewMail(&callback);
+}
+
+class mail::maildir::readKeywordHelper {
+
+ struct maildir_kwReadInfo kri;
+
+ mail::maildir *md;
+
+ static struct libmail_kwMessage
+ **findMessageByFilename(const char *filename,
+ int autocreate,
+ size_t *indexNum,
+ void *voidarg);
+
+ static size_t getMessageCount(void *voidarg);
+ static struct libmail_kwMessage
+ **findMessageByIndex(size_t indexNum,
+ int autocreate,
+ void *voidarg);
+ static const char *getMessageFilename(size_t n, void *voidarg);
+ static struct libmail_kwHashtable *getKeywordHashtable(void *voidarg);
+ static void updateKeywords(size_t n, struct libmail_kwMessage *kw,
+ void *voidarg);
+
+ struct libmail_kwMessage**findMessageByFilename(const char *filename,
+ int autocreate,
+ size_t *indexNum);
+
+ size_t getMessageCount();
+ struct libmail_kwMessage **findMessageByIndex(size_t indexNum,
+ int autocreate);
+ const char *getMessageFilename(size_t n);
+ struct libmail_kwHashtable *getKeywordHashtable();
+ void updateKeywords(size_t n, struct libmail_kwMessage *kw);
+
+ map<string, size_t> filenameMap;
+
+ vector<struct libmail_kwMessage *> kwArray;
+
+
+public:
+ readKeywordHelper(mail::maildir *mdArg);
+ ~readKeywordHelper();
+
+ bool go(string maildir, bool &rc);
+};
+
+
+mail::maildir::readKeywordHelper::readKeywordHelper(mail::maildir *mdArg)
+ : md(mdArg)
+{
+ size_t n=md->index.size();
+ size_t i;
+
+ for (i=0; i<n; i++)
+ {
+ string f=md->index[i].lastKnownFilename;
+ size_t sep=f.rfind(MDIRSEP[0]);
+
+ if (sep != std::string::npos)
+ f=f.substr(0, sep);
+
+ filenameMap.insert(make_pair(f, i));
+ }
+
+ kwArray.resize(n);
+ for (i=0; i<n; i++)
+ kwArray[i]=NULL;
+}
+
+bool mail::maildir::readKeywordHelper::go(string maildir, bool &rc)
+{
+ memset(&kri, 0, sizeof(kri));
+
+ kri.findMessageByFilename= &readKeywordHelper::findMessageByFilename;
+ kri.getMessageCount= &readKeywordHelper::getMessageCount;
+ kri.findMessageByIndex= &readKeywordHelper::findMessageByIndex;
+
+ kri.getMessageFilename= &readKeywordHelper::getMessageFilename;
+ kri.getKeywordHashtable= &readKeywordHelper::getKeywordHashtable;
+ kri.updateKeywords=&readKeywordHelper::updateKeywords;
+ kri.voidarg=this;
+
+ size_t i;
+
+ for (i=0; i<kwArray.size(); i++)
+ {
+ if (kwArray[i])
+ libmail_kwmDestroy(kwArray[i]);
+ kwArray[i]=NULL;
+ }
+
+ char *imap_lock=NULL;
+
+ if (md->lockFolder)
+ {
+ int flag;
+
+ imap_lock=maildir_lock(maildir.c_str(), md->lockFolder,
+ &flag);
+
+ if (!imap_lock)
+ {
+ if (!flag)
+ {
+ rc=false;
+ return false;
+ }
+ }
+ }
+
+ if (maildir_kwRead(maildir.c_str(), &kri) < 0)
+ {
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ }
+
+ rc=false;
+ return false;
+ }
+
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ }
+
+ if (kri.tryagain)
+ return true;
+
+ for (i=0; i<kwArray.size(); i++)
+ {
+ if (kwArray[i] == NULL)
+ {
+ if (!md->index[i].keywords.empty())
+ {
+ md->index[i].changed=true;
+ md->index[i].keywords=
+ mail::keywords::Message();
+ }
+ continue;
+ }
+
+ if (md->index[i].keywords != kwArray[i])
+ {
+ md->index[i].changed=true;
+ md->index[i].keywords.replace(kwArray[i]);
+ kwArray[i]=NULL;
+ }
+ }
+
+ rc=true;
+ return false;
+}
+
+mail::maildir::readKeywordHelper::~readKeywordHelper()
+{
+ size_t i;
+
+ for (i=0; i<kwArray.size(); i++)
+ {
+ if (kwArray[i])
+ libmail_kwmDestroy(kwArray[i]);
+ kwArray[i]=NULL;
+ }
+}
+
+struct libmail_kwMessage **
+mail::maildir::readKeywordHelper::findMessageByFilename(const char *filename,
+ int autocreate,
+ size_t *indexNum,
+ void *voidarg)
+{
+
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->findMessageByFilename(filename, autocreate, indexNum);
+}
+
+size_t mail::maildir::readKeywordHelper::getMessageCount(void *voidarg)
+{
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->getMessageCount();
+}
+
+struct libmail_kwMessage
+**mail::maildir::readKeywordHelper::findMessageByIndex(size_t indexNum,
+ int autocreate,
+ void *voidarg)
+{
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->findMessageByIndex(indexNum, autocreate);
+}
+
+const char *mail::maildir::readKeywordHelper::getMessageFilename(size_t n,
+ void *voidarg)
+{
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->getMessageFilename(n);
+}
+
+struct libmail_kwHashtable *
+mail::maildir::readKeywordHelper::getKeywordHashtable(void *voidarg)
+{
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->getKeywordHashtable();
+}
+
+void mail::maildir::readKeywordHelper::updateKeywords(size_t n,
+ struct libmail_kwMessage *kw,
+ void *voidarg)
+{
+ return ( (mail::maildir::readKeywordHelper *)voidarg)
+ ->updateKeywords(n, kw);
+}
+
+
+
+
+struct libmail_kwMessage **
+mail::maildir::readKeywordHelper::findMessageByFilename(const char *filename,
+ int autocreate,
+ size_t *indexNum)
+{
+ map<string, size_t>::iterator i=filenameMap.find(filename);
+
+ if (i == filenameMap.end())
+ return NULL;
+
+ size_t n=i->second;
+
+ if (indexNum)
+ *indexNum=n;
+
+ if (kwArray[n] == NULL && autocreate)
+ if ((kwArray[n]=libmail_kwmCreate()) == NULL)
+ return NULL;
+ return &kwArray[n];
+}
+
+size_t mail::maildir::readKeywordHelper::getMessageCount()
+{
+ return kwArray.size();
+}
+
+struct libmail_kwMessage
+**mail::maildir::readKeywordHelper::findMessageByIndex(size_t n,
+ int autocreate)
+{
+ if (n >= kwArray.size())
+ return NULL;
+
+ if (kwArray[n] == NULL && autocreate)
+ if ((kwArray[n]=libmail_kwmCreate()) == NULL)
+ return NULL;
+ return &kwArray[n];
+}
+
+const char *mail::maildir::readKeywordHelper::getMessageFilename(size_t n)
+{
+ if (n >= kwArray.size())
+ return NULL;
+
+ return md->index[n].lastKnownFilename.c_str();
+}
+
+struct libmail_kwHashtable *
+mail::maildir::readKeywordHelper::getKeywordHashtable()
+{
+ return &md->keywordHashtable.kwh;
+}
+
+void mail::maildir::readKeywordHelper::updateKeywords(size_t n,
+ struct libmail_kwMessage *kw)
+{
+ if (n >= kwArray.size())
+ return;
+
+ if (kwArray[n])
+ libmail_kwmDestroy(kwArray[n]);
+
+ kwArray[n]=kw;
+}
+
+void mail::maildir::checkNewMail(callback *callback)
+{
+ if (folderPath.size() == 0)
+ {
+ if (callback)
+ callback->success("OK");
+ return;
+ }
+
+ set<string> recentMessages;
+ vector<maildirMessageInfo> newIndex;
+
+ string md;
+
+ char *d=maildir_name2dir(path.c_str(), folderPath.c_str());
+
+ if (!d)
+ {
+ callback->fail("Invalid folder");
+ return;
+ }
+
+ try {
+ md=d;
+ free(d);
+ } catch (...) {
+ free(d);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+
+ // Move messages from new to cur
+
+ maildir_getnew(md.c_str(), NULL,
+ &recent_callback_func, &recentMessages);
+
+ // Now, rescan the cur directory
+
+ scan(folderPath, newIndex);
+
+ // Now, mark messages from from new to cur as recent
+
+ set<string> existingMessages;
+
+ size_t i, n=index.size();
+
+ for (i=0; i<n; i++)
+ existingMessages.insert(index[i].uid);
+
+ bool exists=false;
+
+ for (i=0; i<newIndex.size(); i++)
+ {
+ newIndex[i].recent=
+ recentMessages.count(newIndex[i].lastKnownFilename)>0;
+
+ if (existingMessages.count(newIndex[i].uid) == 0)
+ {
+ exists=true;
+ index.push_back(newIndex[i]);
+ }
+ }
+
+ // Invoke callbacks to reflect deleted, or changed, mail
+
+ MONITOR(mail::maildir);
+
+ mail::expungeList removedList;
+ list<size_t> changedList;
+ while (n > 0)
+ {
+ --n;
+
+ string messageFn=getfilename(n);
+
+ if (messageFn.size() == 0)
+ {
+ index.erase(index.begin() + n);
+
+ removedList << n;
+ continue;
+ }
+ else
+ {
+ index[n].changed=
+ updateFlags(messageFn.c_str(), index[n]);
+
+ index[n].lastKnownFilename=
+ strrchr(messageFn.c_str(), '/')+1;
+ }
+ }
+
+ readKeywordHelper rkh(this);
+
+ bool rc;
+
+ while (rkh.go(md, rc))
+ ;
+
+ if (!rc)
+ {
+ if (callback)
+ callback->fail(strerror(errno));
+ return;
+ }
+
+ n=index.size();
+
+ while (n > 0)
+ {
+ --n;
+
+ if (index[n].changed)
+ changedList.insert(changedList.begin(), n);
+ }
+
+
+ removedList >> folderCallback;
+
+ while (!DESTROYED() && !changedList.empty())
+ {
+ list<size_t>::iterator b=changedList.begin();
+
+ size_t n= *b;
+
+ changedList.erase(b);
+
+ if (folderCallback)
+ folderCallback->messageChanged(n);
+ }
+
+ if (!DESTROYED() && exists && folderCallback)
+ folderCallback->newMessages();
+
+ if (callback)
+ callback->success("OK");
+}
+
+
+
+
+void mail::maildir::genericMarkRead(size_t messageNumber)
+{
+ if (messageNumber < index.size() && index[messageNumber].unread)
+ {
+ index[messageNumber].unread=false;
+ updateFlags(messageNumber);
+ }
+}
+
+bool mail::maildir::hasCapability(string capability)
+{
+ return false;
+}
+
+string mail::maildir::getCapability(string name)
+{
+ upper(name);
+
+ if (name == LIBMAIL_SERVERTYPE)
+ {
+ return "maildir";
+ }
+
+ if (name == LIBMAIL_SERVERDESCR)
+ {
+ return "Local maildir";
+ }
+
+ return "";
+}
+
+void mail::maildir::readMessageAttributes(const vector<size_t> &messages,
+ MessageAttributes attributes,
+ mail::callback::message &callback)
+{
+ genericAttributes(this, this, messages, attributes, callback);
+}
+
+void mail::maildir::readMessageContent(const vector<size_t> &messages,
+ bool peek,
+ enum mail::readMode readType,
+ mail::callback::message &callback)
+{
+ genericReadMessageContent(this, this, messages, peek, readType,
+ callback);
+}
+
+void mail::maildir::readMessageContent(size_t messageNum,
+ bool peek,
+ const class mail::mimestruct &msginfo,
+ enum mail::readMode readType,
+ mail::callback::message &callback)
+{
+ genericReadMessageContent(this, this, messageNum, peek, msginfo,
+ readType, callback);
+}
+
+void mail::maildir::readMessageContentDecoded(size_t messageNum,
+ bool peek,
+ const mail::mimestruct &msginfo,
+ mail::callback::message
+ &callback)
+{
+ genericReadMessageContentDecoded(this, this, messageNum, peek,
+ msginfo, callback);
+}
+
+size_t mail::maildir::getFolderIndexSize()
+{
+ return index.size();
+}
+
+mail::messageInfo mail::maildir::getFolderIndexInfo(size_t msgNum)
+{
+ if (msgNum < index.size())
+ return index[msgNum];
+
+ return mail::messageInfo();
+}
+
+void mail::maildir::saveFolderIndexInfo(size_t msgNum,
+ const mail::messageInfo &info,
+ mail::callback &callback)
+{
+ if (msgNum >= index.size())
+ {
+ callback.success("OK");
+ return;
+ }
+
+ mail::messageInfo &newFlags=index[msgNum];
+
+#define DOFLAG(dummy, field, dummy2) \
+ newFlags.field=info.field;
+
+ LIBMAIL_MSGFLAGS;
+
+#undef DOFLAG
+
+ string errmsg="Message updated";
+
+ if (!updateFlags(msgNum))
+ errmsg="Folder opened in read-only mode.";
+
+ callback.success(errmsg);
+}
+
+bool mail::maildir::updateFlags(size_t msgNum)
+{
+ bool changed=true;
+
+ string messageFn=getfilename(msgNum);
+
+ if (messageFn.size() > 0)
+ {
+ string s(strrchr(messageFn.c_str(), '/')+1);
+
+ size_t i=s.find(MDIRSEP[0]);
+
+ if (i != std::string::npos)
+ s=s.substr(0, i);
+
+ s += MDIRSEP "2,";
+
+ s += getMaildirFlags(index[msgNum]);
+
+ const char *fnP=messageFn.c_str();
+
+ string newName=string(fnP, (const char *)strrchr(fnP, '/')+1)
+ + s;
+
+ if (newName != messageFn)
+ {
+ if (rename(messageFn.c_str(), newName.c_str()) == 0)
+ {
+ index[msgNum].lastKnownFilename=s;
+ }
+ else
+ changed=false;
+ }
+
+ messageFn=getfilename(msgNum);
+
+ if (folderCallback)
+ folderCallback->messageChanged(msgNum);
+ }
+
+ return changed;
+}
+
+string mail::maildir::getMaildirFlags(const mail::messageInfo &flags)
+{
+ string s="";
+
+ if (flags.draft)
+ s += "D";
+
+ if (flags.replied)
+ s += "R";
+
+ if (flags.marked)
+ s += "F";
+
+ if (!flags.unread)
+ s += "S";
+
+ if (flags.deleted)
+ s += "T";
+ return s;
+}
+
+void mail::maildir::updateFolderIndexFlags(const vector<size_t> &messages,
+ bool doFlip,
+ bool enableDisable,
+ const mail::messageInfo &flags,
+ mail::callback &callback)
+{
+ string errmsg="Message updated";
+
+ vector<size_t>::const_iterator b, e;
+
+ b=messages.begin();
+ e=messages.end();
+
+ size_t n=index.size();
+
+ MONITOR(mail::maildir);
+
+ while (!DESTROYED() && b != e)
+ {
+ size_t i= *b++;
+
+ if (i < n)
+ {
+#define DOFLAG(dummy, field, dummy2) \
+ if (flags.field) \
+ { \
+ index[i].field=\
+ doFlip ? !index[i].field\
+ : enableDisable; \
+ }
+
+ LIBMAIL_MSGFLAGS;
+#undef DOFLAG
+ }
+
+ if (!updateFlags(i))
+ errmsg="Folder opened in read-only mode.";
+
+ }
+
+ callback.success(errmsg);
+}
+
+void mail::maildir::removeMessages(const std::vector<size_t> &messages,
+ callback &cb)
+{
+ vector<size_t>::const_iterator b=messages.begin(), e=messages.end();
+
+ while (b != e)
+ {
+ size_t n=*b++;
+
+ string messageFn=getfilename(n);
+
+ if (messageFn.size() > 0)
+ unlink(messageFn.c_str());
+ }
+ updateFolderIndexInfo(&cb, false);
+}
+
+void mail::maildir::updateFolderIndexInfo(mail::callback &callback)
+{
+ updateFolderIndexInfo(&callback, true);
+}
+
+void mail::maildir::updateFolderIndexInfo(mail::callback *callback,
+ bool doExpunge)
+{
+ if (folderPath.size() == 0)
+ {
+ if (callback)
+ callback->success("OK");
+ return;
+ }
+
+ struct stat stat_buf;
+
+ size_t n=index.size();
+
+ mail::expungeList removedList;
+
+ while (n > 0)
+ {
+ --n;
+
+ string messageFn=getfilename(n);
+
+ if (doExpunge)
+ {
+ if (!index[n].deleted && messageFn.size() > 0)
+ continue;
+
+ if (messageFn.size() > 0)
+ {
+ unlink(messageFn.c_str());
+ messageFn="";
+ }
+ }
+
+ if (messageFn.size() == 0 ||
+ stat(messageFn.c_str(), &stat_buf))
+ {
+ index.erase(index.begin() + n);
+
+ removedList << n;
+ }
+ }
+
+ removedList >> folderCallback;
+
+ checkNewMail(callback);
+}
+
+void mail::maildir::copyMessagesTo(const vector<size_t> &messages,
+ mail::folder *copyTo,
+ mail::callback &callback)
+{
+ mail::copyMessages::copy(this, messages, copyTo, callback);
+}
+
+void mail::maildir::searchMessages(const mail::searchParams &searchInfo,
+ mail::searchCallback &callback)
+{
+ mail::searchMessages::search(callback, searchInfo, this);
+}
+
+// Open a new folder.
+
+void mail::maildir::open(string pathStr, mail::callback &callback,
+ mail::callback::folder &folderCallbackArg)
+{
+ index.clear();
+ updateNotify(false);
+
+ folderCallback=NULL;
+ if (cacheRfcp)
+ {
+ rfc2045_free(cacheRfcp);
+ cacheRfcp=NULL;
+ }
+
+ if (cachefd >= 0)
+ {
+ close(cachefd);
+ cachefd=-1;
+ }
+
+ if (path == "")
+ {
+ callback.fail(strerror(errno));
+ return;
+ }
+
+ set<string> recentMessages;
+
+ string md;
+
+ char *d=maildir_name2dir(path.c_str(), pathStr.c_str());
+
+ if (!d)
+ {
+ callback.fail(strerror(errno));
+ return;
+ }
+
+ try {
+ md=d;
+ free(d);
+ } catch (...) {
+ free(d);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+
+ {
+ struct stat stat_buf;
+
+ string d=md + "/" KEYWORDDIR;
+ /* New Courier-IMAP maildirwatch code */
+
+ mkdir(d.c_str(), 0700);
+
+ if (stat(md.c_str(), &stat_buf) == 0)
+ chmod(d.c_str(), stat_buf.st_mode & 0777);
+ }
+
+ maildir_purgetmp(md.c_str());
+ maildir_getnew(md.c_str(), NULL,
+ &recent_callback_func, &recentMessages);
+
+ scan(pathStr, index);
+
+ folderCallback= &folderCallbackArg;
+ folderPath=pathStr;
+
+ vector<maildirMessageInfo>::iterator b=index.begin(), e=index.end();
+
+ while (b != e)
+ {
+ b->recent=recentMessages.count(b->lastKnownFilename) > 0;
+ b++;
+ }
+
+ if (lockFolder)
+ maildirwatch_free(lockFolder);
+
+ lockFolder=maildirwatch_alloc(md.c_str());
+
+ readKeywordHelper rkh(this);
+
+ bool rc;
+
+ while (rkh.go(md, rc))
+ ;
+
+ callback.success("Mail folder opened");
+}
+
+/*-------------------------------------------------------------------------*/
+
+void mail::maildir::genericMessageRead(string uid,
+ size_t messageNumber,
+ bool peek,
+ mail::readMode readType,
+ mail::callback::message &callback)
+{
+ if (!fixMessageNumber(this, uid, messageNumber))
+ {
+ callback.success("OK");
+ return;
+ }
+
+ MONITOR(mail::maildir);
+
+ string messageFn=getfilename(messageNumber);
+
+ if (messageFn.size() == 0)
+ {
+ callback.success("OK");
+ return;
+ }
+
+ int fd=maildir_safeopen(messageFn.c_str(), O_RDONLY, 0);
+
+ if (fd < 0)
+ {
+ callback.success("OK");
+ return;
+ }
+
+ try {
+ mail::file f(fd, "r");
+
+ f.genericMessageRead(this, messageNumber, readType, callback);
+
+ if (ferror(f))
+ {
+ callback.fail(strerror(errno));
+ close(fd);
+ return;
+ }
+
+ } catch (...) {
+ close(fd);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+
+ close(fd);
+
+ if (!peek && index[messageNumber].unread)
+ {
+ index[messageNumber].unread=false;
+ saveFolderIndexInfo(messageNumber,
+ index[messageNumber],
+ callback);
+ return;
+ }
+
+ callback.success("OK");
+}
+
+void mail::maildir::genericMessageSize(string uid,
+ size_t messageNumber,
+ mail::callback::message &callback)
+{
+ if (!fixMessageNumber(this, uid, messageNumber))
+ {
+ callback.messageSizeCallback(messageNumber, 0);
+ callback.success("OK");
+ return;
+ }
+
+ string messageFn=getfilename(messageNumber);
+
+ if (messageFn.size() == 0)
+ {
+ callback.messageSizeCallback(messageNumber,0);
+ callback.success("OK");
+ return;
+ }
+
+ unsigned long ul;
+
+ if (maildir_parsequota(messageFn.c_str(), &ul) == 0)
+ {
+ callback.messageSizeCallback(messageNumber, ul);
+ callback.success("OK");
+ return;
+ }
+
+ struct stat stat_buf;
+
+ int rc=stat(messageFn.c_str(), &stat_buf);
+
+ callback.messageSizeCallback(messageNumber,
+ rc == 0 ? stat_buf.st_size:0);
+ callback.success("OK");
+}
+
+void mail::maildir::genericGetMessageFd(string uid,
+ size_t messageNumber,
+ bool peek,
+ int &fdRet,
+ mail::callback &callback)
+{
+ struct rfc2045 *dummy;
+
+ genericGetMessageFdStruct(uid, messageNumber, peek, fdRet, dummy,
+ callback);
+}
+
+void mail::maildir::genericGetMessageStruct(string uid,
+ size_t messageNumber,
+ struct rfc2045 *&structRet,
+ mail::callback &callback)
+{
+ int dummy;
+
+ genericGetMessageFdStruct(uid, messageNumber, true, dummy, structRet,
+ callback);
+}
+
+bool mail::maildir::genericCachedUid(string uid)
+{
+ return uid == cacheUID && cachefd >= 0 && cacheRfcp != 0;
+}
+
+void mail::maildir::genericGetMessageFdStruct(string uid,
+ size_t messageNumber,
+ bool peek,
+ int &fdRet,
+ struct rfc2045 *&structret,
+ mail::callback &callback)
+{
+ if (uid == cacheUID && cachefd >= 0 && cacheRfcp != 0)
+ {
+ fdRet=cachefd;
+ structret=cacheRfcp;
+ callback.success("OK");
+ return;
+ }
+
+ if (!fixMessageNumber(this, uid, messageNumber))
+ {
+ callback.fail("Message removed on the server");
+ return;
+ }
+
+ if (cacheRfcp)
+ {
+ rfc2045_free(cacheRfcp);
+ cacheRfcp=NULL;
+ }
+
+ if (cachefd >= 0)
+ {
+ close(cachefd);
+ cachefd= -1;
+ }
+
+ if (!fixMessageNumber(this, uid, messageNumber))
+ {
+ callback.fail("Message removed on the server");
+ return;
+ }
+
+ string messageFn=getfilename(messageNumber);
+
+ if (messageFn.size() == 0)
+ {
+ callback.fail("Message removed on the server");
+ return;
+ }
+
+ int fd=maildir_safeopen(messageFn.c_str(), O_RDONLY, 0);
+
+ struct rfc2045 *rfcp;
+
+ if (fd < 0 || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
+ (rfcp=rfc2045_alloc()) == NULL)
+ {
+ if (fd >= 0)
+ close(fd);
+ callback.fail("Message removed on the server");
+ return;
+ }
+
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ cachefd=fd;
+ cacheRfcp=rfcp;
+ cacheUID="";
+
+ vector<char> buffer;
+
+ buffer.insert(buffer.end(), BUFSIZ, 0);
+
+ int n;
+
+ while ((n=read(fd, &buffer[0], buffer.size())) > 0)
+ rfc2045_parse(rfcp, &buffer[0], n);
+ rfc2045_parse_partial(rfcp);
+
+ if (n < 0)
+ {
+ callback.fail(strerror(errno));
+ return;
+ }
+
+ fdRet=cachefd;
+ structret=cacheRfcp;
+ if (!peek && index[messageNumber].unread)
+ {
+ index[messageNumber].unread=false;
+ saveFolderIndexInfo(messageNumber, index[messageNumber],
+ callback);
+ return;
+ }
+ callback.success("OK");
+}
+
+void mail::maildir::updateNotify(bool enableDisable,
+ mail::callback &callback)
+{
+ updateNotify(enableDisable);
+ if (!enableDisable)
+ updateFolderIndexInfo(&callback, false);
+ else
+ callback.success("OK");
+}
+
+void mail::maildir::updateNotify(bool enableDisable)
+{
+ if (!enableDisable)
+ {
+ if (watchFolder)
+ {
+ maildirwatch_end(&watchFolderContents);
+ maildirwatch_free(watchFolder);
+ watchFolder=NULL;
+ }
+ return;
+ }
+
+ if (folderPath.size() == 0 || watchFolder)
+ return;
+
+ char *dir=maildir_name2dir(path.c_str(), folderPath.c_str());
+
+ if (!dir)
+ return;
+
+ watchFolder=maildirwatch_alloc(dir);
+
+ free(dir);
+
+ if (!watchFolder)
+ return;
+
+ if (maildirwatch_start(watchFolder, &watchFolderContents) < 0)
+ {
+ maildirwatch_free(watchFolder);
+ return;
+ }
+ watchStarting=true;
+}
+
+
+void mail::maildir::getFolderKeywordInfo(size_t n, std::set<std::string> &s)
+{
+ string messageFn=getfilename(n);
+
+ if (messageFn.size() == 0)
+ return;
+
+ index[n].keywords.getFlags(s);
+}
+
+void mail::maildir::updateKeywords(const vector<size_t> &messages,
+ const set<string> &keywords,
+ bool setOrChange,
+ bool changeTo,
+ mail::callback &cb)
+{
+ bool keepGoing;
+
+ if (folderPath.size() == 0)
+ {
+ cb.success("Ok.");
+ return;
+ }
+
+ string dir;
+
+ {
+ char *dirs=maildir_name2dir(path.c_str(), folderPath.c_str());
+ if (!dirs)
+ {
+ cb.fail(strerror(errno));
+ return;
+ }
+
+ try {
+ dir=dirs;
+ free(dirs);
+ } catch (...) {
+ free(dirs);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+ }
+
+ if (!setOrChange)
+ {
+ if (!updateKeywords(dir,
+ messages, keywords, setOrChange, changeTo,
+ NULL, NULL))
+ {
+ cb.fail(strerror(errno));
+ return;
+ }
+ }
+ else do {
+ struct libmail_kwGeneric g;
+
+ libmail_kwgInit(&g);
+
+ char *imap_lock=NULL;
+
+ if (lockFolder)
+ {
+ int flag;
+
+ imap_lock=maildir_lock(dir.c_str(), lockFolder,
+ &flag);
+
+ if (!imap_lock)
+ {
+ if (!flag)
+ {
+ libmail_kwgDestroy(&g);
+ cb.fail(strerror(errno));
+ return;
+ }
+ }
+ }
+
+ try {
+ if (libmail_kwgReadMaildir(&g, dir.c_str()) < 0)
+ {
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ imap_lock=NULL;
+ }
+
+ cb.fail(strerror(errno));
+ return;
+ }
+
+ keepGoing=false;
+
+ if (!updateKeywords(dir,
+ messages, keywords, setOrChange,
+ changeTo,
+ &g, &keepGoing))
+ {
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ imap_lock=NULL;
+ }
+ libmail_kwgDestroy(&g);
+ if (keepGoing)
+ continue;
+
+ cb.fail(strerror(errno));
+ return;
+ }
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ imap_lock=NULL;
+ }
+ libmail_kwgDestroy(&g);
+ } catch (...) {
+ if (imap_lock)
+ {
+ unlink(imap_lock);
+ free(imap_lock);
+ imap_lock=NULL;
+ }
+ libmail_kwgDestroy(&g);
+ throw;
+ }
+ } while (keepGoing);
+
+ cb.success("Message keywords updated.");
+}
+
+
+bool mail::maildir::updateKeywords(string dir,
+ const vector<size_t> &messages,
+ const set<string> &keywords,
+ bool setOrChange,
+ bool changeTo,
+ struct libmail_kwGeneric *g,
+ bool *keepGoing)
+{
+ mail::keywords::Message m;
+
+ m.setFlags(keywordHashtable, keywords);
+
+ MONITOR(mail::maildir);
+
+ vector<size_t>::const_iterator b=messages.begin(),
+ e=messages.end();
+
+ while (!DESTROYED() && b != e)
+ {
+ size_t n= *b++;
+
+ string messageFn=getfilename(n);
+
+ if (messageFn.size() == 0)
+ continue;
+
+ if (!setOrChange)
+ {
+ set<string> newFlags;
+
+ m.getFlags(newFlags);
+ index[n].keywords.setFlags(keywordHashtable,
+ newFlags);
+ // Can't copy, because of reference counting.
+
+ char *tmpname, *newname;
+
+ if (maildir_kwSave(dir.c_str(),
+ index[n].lastKnownFilename
+ .c_str(), newFlags,
+ &tmpname, &newname, 0) < 0)
+ return false;
+
+ int rc=rename(tmpname, newname);
+ free(tmpname);
+ free(newname);
+
+ if (rc < 0)
+ return false;
+ }
+ else if (changeTo)
+ {
+ struct libmail_kwGenericEntry *origKw=
+ libmail_kwgFindByName(g, index[n]
+ .lastKnownFilename
+ .c_str());
+
+ set<string>::iterator b=keywords.begin(),
+ e=keywords.end();
+
+ while (b != e)
+ {
+ if (!index[n].keywords
+ .addFlag(keywordHashtable, *b))
+ {
+ LIBMAIL_THROW(strerror(errno));
+ }
+
+ if (origKw && origKw->keywords &&
+ libmail_kwmSetName(&g->kwHashTable,
+ origKw->keywords,
+ b->c_str()))
+ LIBMAIL_THROW(strerror(errno));
+
+ ++b;
+ }
+
+ char *tmpname, *newname;
+
+ set<string> newFlags;
+
+ index[n].keywords.getFlags(newFlags);
+
+ if ((origKw && origKw->keywords ?
+ maildir_kwSave(dir.c_str(),
+ index[n].lastKnownFilename
+ .c_str(), origKw->keywords,
+ &tmpname, &newname,
+ 1):
+ maildir_kwSave(dir.c_str(),
+ index[n].lastKnownFilename
+ .c_str(), newFlags,
+ &tmpname, &newname,
+ 1)) < 0)
+ return false;
+
+ int rc=link(tmpname, newname);
+ unlink(tmpname);
+ free(tmpname);
+ free(newname);
+
+ if (rc < 0)
+ {
+ if (errno == EEXIST)
+ *keepGoing=true;
+ return false;
+ }
+ }
+ else
+ {
+ struct libmail_kwGenericEntry *origKw=
+ libmail_kwgFindByName(g, index[n]
+ .lastKnownFilename
+ .c_str());
+
+ set<string>::iterator b=keywords.begin(),
+ e=keywords.end();
+
+ while (b != e)
+ {
+ struct libmail_keywordEntry *kef;
+
+ if (!index[n].keywords.remFlag(*b))
+ LIBMAIL_THROW(strerror(errno));
+
+ if (origKw && origKw->keywords &&
+ (kef=libmail_kweFind(&g->kwHashTable,
+ b->c_str(),
+ 0)) != NULL)
+ {
+ libmail_kwmClear(origKw->keywords,
+ kef);
+ }
+ ++b;
+ }
+ char *tmpname, *newname;
+ set<string> newFlags;
+
+ index[n].keywords.getFlags(newFlags);
+
+ if ((origKw && origKw->keywords
+ ? maildir_kwSave(dir.c_str(),
+ index[n].lastKnownFilename
+ .c_str(), origKw->keywords,
+ &tmpname, &newname,
+ 1)
+ : maildir_kwSave(dir.c_str(),
+ index[n].lastKnownFilename
+ .c_str(), newFlags,
+ &tmpname, &newname,
+ 1)) < 0)
+ return false;
+
+ int rc=link(tmpname, newname);
+ unlink(tmpname);
+ free(tmpname);
+ free(newname);
+
+ if (rc < 0)
+ {
+ if (errno == EEXIST)
+ *keepGoing=true;
+ return false;
+ }
+ }
+
+ if (folderCallback)
+ folderCallback->messageChanged(n);
+ }
+ return true;
+}