From 31cd25b5b05a8d0ae41c16f504566efde43b3a68 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sat, 4 Dec 2004 17:48:02 +0000 Subject: [PATCH] Some update for 'maildir' protocol. --- src/messaging/maildirFolder.cpp | 292 +++++++++++++++++++++++++++---- src/messaging/maildirFolder.hpp | 19 +- src/messaging/maildirMessage.hpp | 2 + src/messaging/maildirUtils.cpp | 38 +++- src/messaging/maildirUtils.hpp | 53 +++++- 5 files changed, 359 insertions(+), 45 deletions(-) diff --git a/src/messaging/maildirFolder.cpp b/src/messaging/maildirFolder.cpp index 155b48fc..8ae6b72c 100644 --- a/src/messaging/maildirFolder.cpp +++ b/src/messaging/maildirFolder.cpp @@ -261,59 +261,113 @@ void maildirFolder::scanFolder() { utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); - utility::auto_ptr newDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW)); - utility::auto_ptr curDir = fsf->create - (maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR)); + utility::file::path newDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_NEW); + utility::auto_ptr newDir = fsf->create(newDirPath); - // Unread messages (new/) + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + utility::auto_ptr curDir = fsf->create(curDirPath); + + // New received messages (new/) utility::auto_ptr nit = newDir->getFiles(); - std::vector unreadMessageFilenames; + std::vector newMessageFilenames; while (nit->hasMoreElements()) { utility::auto_ptr file = nit->nextElement(); - unreadMessageFilenames.push_back(file->fullPath().getLastComponent()); + newMessageFilenames.push_back(file->fullPath().getLastComponent()); } - // Seen messages (cur/) + // Current messages (cur/) utility::auto_ptr cit = curDir->getFiles(); - std::vector messageFilenames; + std::vector curMessageFilenames; while (cit->hasMoreElements()) { utility::auto_ptr file = cit->nextElement(); - messageFilenames.push_back(file->fullPath().getLastComponent()); + curMessageFilenames.push_back(file->fullPath().getLastComponent()); } - // TODO: update m_messageFilenames - // TODO: what to do with files which name has changed? (flag change, message deletion...) + // Update/delete existing messages (found in previous scan) + for (unsigned int i = 0 ; i < m_messageInfos.size() ; ++i) + { + messageInfos& msgInfos = m_messageInfos[i]; - m_unreadMessageCount = unreadMessageFilenames.size(); - m_messageCount = messageFilenames.size(); + // NOTE: the flags may have changed (eg. moving from 'new' to 'cur' + // may imply the 'S' flag) and so the filename. That's why we use + // "maildirUtils::messageIdComparator" to compare only the 'unique' + // portion of the filename... + + if (msgInfos.type == messageInfos::TYPE_CUR) + { + const std::vector ::iterator pos = + std::find_if(curMessageFilenames.begin(), curMessageFilenames.end(), + maildirUtils::messageIdComparator(msgInfos.path)); + + // If we cannot find this message in the 'cur' directory, + // it means it has been deleted (and expunged). + if (pos == curMessageFilenames.end()) + { + msgInfos.type = messageInfos::TYPE_DELETED; + } + // Otherwise, update its information. + else + { + msgInfos.path = *pos; + curMessageFilenames.erase(pos); + } + } + } + + // Add new messages from 'new': we are responsible to move the files + // from the 'new' directory to the 'cur' directory, and append them + // to our message list. + for (std::vector ::const_iterator + it = newMessageFilenames.begin() ; it != newMessageFilenames.end() ; ++it) + { + // Move messages from 'new' to 'cur' + utility::auto_ptr file = fsf->create(newDirPath / *it); + file->rename(curDirPath / *it); + + // Append to message list + messageInfos msgInfos; + msgInfos.path = *it; + msgInfos.type = messageInfos::TYPE_CUR; + + m_messageInfos.push_back(msgInfos); + } + + // Add new messages from 'cur': the files have already been moved + // from 'new' to 'cur'. Just append them to our message list. + for (std::vector ::const_iterator + it = curMessageFilenames.begin() ; it != curMessageFilenames.end() ; ++it) + { + // Append to message list + messageInfos msgInfos; + msgInfos.path = *it; + msgInfos.type = messageInfos::TYPE_CUR; + + m_messageInfos.push_back(msgInfos); + } + + // Update message count + int unreadMessageCount = 0; + + for (std::vector ::const_iterator + it = m_messageInfos.begin() ; it != m_messageInfos.end() ; ++it) + { + if ((maildirUtils::extractFlags((*it).path) & message::FLAG_SEEN) == 0) + ++unreadMessageCount; + } + + m_unreadMessageCount = unreadMessageCount; + m_messageCount = m_messageInfos.size(); } catch (exceptions::filesystem_exception&) { // Should not happen... } - - /* - int m_unreadMessageCount; - int m_messageCount; - - std::vector m_unreadMessageFilenames; - std::vector m_messageFilenames; - - if (0) - { - m_messageFilenames.clear(); - - for (...) - { - m_messageFilenames.push_back(...); - } - } - */ } @@ -436,19 +490,187 @@ void maildirFolder::rename(const folder::path& newPath) void maildirFolder::deleteMessage(const int num) { - // TODO + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + if (m_messageInfos[num].type == messageInfos::TYPE_DELETED) + return; + + m_messageInfos[num].type = messageInfos::TYPE_DELETED; + + // Delete file from file system + try + { + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + utility::auto_ptr file = fsf->create + (curDirPath / m_messageInfos[num].path); + + file->remove(); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + + // Update local flags + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() == num && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector nums; + nums.push_back(num); + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); } void maildirFolder::deleteMessages(const int from, const int to) { - // TODO + if (from < 1 || (to < from && to != -1)) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + const int to2 = (to == -1) ? m_messageCount : to; + const int count = to - from + 1; + + // Delete files from file system + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + for (int i = from ; i <= to2 ; ++i) + { + if (m_messageInfos[i].type != messageInfos::TYPE_DELETED) + { + m_messageInfos[i].type = messageInfos::TYPE_DELETED; + + try + { + utility::auto_ptr file = fsf->create + (curDirPath / m_messageInfos[i].path); + + file->remove(); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + } + } + + // Update local flags + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + std::vector nums; + nums.resize(count); + + for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) + nums[j] = i; + + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); } void maildirFolder::deleteMessages(const std::vector & nums) { - // TODO + if (nums.empty()) + throw exceptions::invalid_argument(); + + if (!m_store) + throw exceptions::illegal_state("Store disconnected"); + else if (!isOpen()) + throw exceptions::illegal_state("Folder not open"); + else if (m_mode == MODE_READ_ONLY) + throw exceptions::illegal_state("Folder is read-only"); + + // Sort the list of message numbers + std::vector list; + + list.resize(nums.size()); + std::copy(nums.begin(), nums.end(), list.begin()); + + std::sort(list.begin(), list.end()); + + // Delete files from file system + utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = maildirUtils::getFolderFSPath + (m_store, m_path, maildirUtils::FOLDER_PATH_CUR); + + for (std::vector ::const_iterator it = + list.begin() ; it != list.end() ; ++it) + { + const int num = *it; + + if (m_messageInfos[num].type != messageInfos::TYPE_DELETED) + { + m_messageInfos[num].type = messageInfos::TYPE_DELETED; + + try + { + utility::auto_ptr file = fsf->create + (curDirPath / m_messageInfos[num].path); + + file->remove(); + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) + } + } + } + + + // Update local flags + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + if (std::binary_search(list.begin(), list.end(), (*it)->getNumber())) + { + if ((*it)->m_flags != message::FLAG_UNDEFINED) + (*it)->m_flags |= message::FLAG_DELETED; + } + } + + // Notify message flags changed + events::messageChangedEvent event(this, events::messageChangedEvent::TYPE_FLAGS, list); + + notifyMessageChanged(event); } diff --git a/src/messaging/maildirFolder.hpp b/src/messaging/maildirFolder.hpp index 65835219..96883736 100644 --- a/src/messaging/maildirFolder.hpp +++ b/src/messaging/maildirFolder.hpp @@ -27,6 +27,8 @@ #include "../types.hpp" #include "folder.hpp" +#include "../utility/file.hpp" + namespace vmime { namespace messaging { @@ -134,9 +136,22 @@ private: int m_unreadMessageCount; int m_messageCount; - std::vector m_unreadMessageFilenames; - std::vector m_messageFilenames; + // Store information about scanned messages + struct messageInfos + { + enum Type + { + TYPE_CUR, + TYPE_DELETED + }; + utility::file::path::component path; // filename + Type type; // current location + }; + + std::vector m_messageInfos; + + // Instanciated message objects std::vector m_messages; }; diff --git a/src/messaging/maildirMessage.hpp b/src/messaging/maildirMessage.hpp index 83e7758f..7d2cd0d6 100644 --- a/src/messaging/maildirMessage.hpp +++ b/src/messaging/maildirMessage.hpp @@ -77,6 +77,8 @@ protected: maildirFolder* m_folder; int m_num; + int m_size; + int m_flags; }; diff --git a/src/messaging/maildirUtils.cpp b/src/messaging/maildirUtils.cpp index 2d294c0f..f6e16d8a 100644 --- a/src/messaging/maildirUtils.cpp +++ b/src/messaging/maildirUtils.cpp @@ -85,13 +85,23 @@ const bool maildirUtils::isSubfolderDirectory(const utility::file& file) } -/* +const utility::file::path::component maildirUtils::extractId + (const utility::file::path::component& filename) +{ + string::size_type sep = filename.getBuffer().rfind(':'); + if (sep == string::npos) return (filename); + + return (utility::path::component + (string(filename.getBuffer().begin(), filename.getBuffer().begin() + sep))); +} + + const int maildirUtils::extractFlags(const utility::file::path::component& comp) { - string::size_type sep = comp.buffer().rfind(':'); + string::size_type sep = comp.getBuffer().rfind(':'); if (sep == string::npos) return (0); - const string flagsString(comp.buffer().begin() + sep + 1, comp.buffer().end()); + const string flagsString(comp.getBuffer().begin() + sep + 1, comp.getBuffer().end()); const string::size_type count = flagsString.length(); int flags = 0; @@ -102,6 +112,8 @@ const int maildirUtils::extractFlags(const utility::file::path::component& comp) { case 'S': case 's': flags |= message::FLAG_SEEN; break; case 'R': case 'r': flags |= message::FLAG_REPLIED; break; + + // TODO: more flags } } @@ -109,6 +121,7 @@ const int maildirUtils::extractFlags(const utility::file::path::component& comp) } +/* const utility::file::component maildirUtils::changeFlags (const utility::file::component& comp, const int flags) { @@ -116,5 +129,24 @@ const utility::file::component maildirUtils::changeFlags */ + +// +// messageIdComparator +// + +maildirUtils::messageIdComparator::messageIdComparator + (const utility::file::path::component& comp) + : m_comp(maildirUtils::extractId(comp)) +{ +} + + +const bool maildirUtils::messageIdComparator::operator() + (const utility::file::path::component& other) const +{ + return (m_comp == maildirUtils::extractId(other)); +} + + } // messaging } // vmime diff --git a/src/messaging/maildirUtils.hpp b/src/messaging/maildirUtils.hpp index 44d36ad3..dddc735d 100644 --- a/src/messaging/maildirUtils.hpp +++ b/src/messaging/maildirUtils.hpp @@ -32,18 +32,37 @@ namespace messaging { class maildirStore; +/** Miscellaneous helpers functions for maildir messaging system. + */ + class maildirUtils { public: + /** Comparator for message filenames, based only on the + * unique identifier part of the filename. + */ + class messageIdComparator + { + public: + + messageIdComparator(const utility::file::path::component& comp); + + const bool operator()(const utility::file::path::component& other) const; + + private: + + const utility::file::path::component m_comp; + }; + /** Mode for return value of getFolderFSPath(). */ enum FolderFSPathMode { - FOLDER_PATH_ROOT, /**< Root folder (eg. ~/Mail/MyFolder) */ - FOLDER_PATH_NEW, /**< Folder containing unread messages (eg. ~/Mail/MyFolder/new) */ - FOLDER_PATH_CUR, /**< Folder containing messages that have been seen (eg. ~/Mail/MyFolder/cur) */ - FOLDER_PATH_TMP, /**< Temporary folder used for reliable delivery (eg. ~/Mail/MyFolder/tmp) */ - FOLDER_PATH_CONTAINER /**< Container for sub-folders (eg. ~/Mail/.MyFolder.directory) */ + FOLDER_PATH_ROOT, /**< Root folder. Eg: ~/Mail/MyFolder */ + FOLDER_PATH_NEW, /**< Folder containing unread messages. Eg: ~/Mail/MyFolder/new */ + FOLDER_PATH_CUR, /**< Folder containing messages that have been seen. Eg: ~/Mail/MyFolder/cur */ + FOLDER_PATH_TMP, /**< Temporary folder used for reliable delivery. Eg: ~/Mail/MyFolder/tmp */ + FOLDER_PATH_CONTAINER /**< Container for sub-folders. Eg: ~/Mail/.MyFolder.directory */ }; /** Return the path on the filesystem for the folder in specified store. @@ -55,8 +74,32 @@ public: */ static const utility::file::path getFolderFSPath(maildirStore* store, const utility::path& folderPath, const FolderFSPathMode mode); + /** Test whether the specified file-system directory corresponds to + * a maildir sub-folder. The name of the directory should not start + * with '.' to be listed as a sub-folder. + * + * @return true if the specified directory is a maildir sub-folder, + * false otherwise + */ static const bool isSubfolderDirectory(const utility::file& file); + /** Extract the unique identifier part of the message filename. + * Eg: for the filename "1071577232.28549.m03s:2,RS", it will + * return "1071577232.28549.m03s". + * + * @return part of the filename that corresponds to the unique + * identifier of the message + */ + static const utility::file::path::component extractId(const utility::file::path::component& filename); + + /** Extract message flags from the specified message filename. + * Eg: for the filename "1071577232.28549.m03s:2,RS", it will + * return (message::FLAG_SEEN | message::FLAG_REPLIED). + * + * @return message flags extracted from the specified filename + */ + static const int extractFlags(const utility::file::path::component& comp); + private: static const vmime::word TMP_DIR;