diff options
author | Vincent Richard <[email protected]> | 2013-07-26 12:47:30 +0000 |
---|---|---|
committer | Vincent Richard <[email protected]> | 2013-07-26 12:47:30 +0000 |
commit | b0845eff0db7d3aa35c3dc1629d250535d704bc8 (patch) | |
tree | 3e317af991e2f02596916cda0a0cb7b552bcd998 | |
parent | Added SMTP service properties to allow disabling PIPELINING and CHUNKING exte... (diff) | |
download | vmime-b0845eff0db7d3aa35c3dc1629d250535d704bc8.tar.gz vmime-b0845eff0db7d3aa35c3dc1629d250535d704bc8.zip |
Allow messages to be designated either by their number or their UID.
Warning: this is an API-breaking change.
-rw-r--r-- | SConstruct | 2 | ||||
-rw-r--r-- | doc/book/net.tex | 14 | ||||
-rw-r--r-- | src/net/imap/IMAPFolder.cpp | 361 | ||||
-rw-r--r-- | src/net/imap/IMAPMessage.cpp | 5 | ||||
-rw-r--r-- | src/net/imap/IMAPUtils.cpp | 213 | ||||
-rw-r--r-- | src/net/maildir/maildirFolder.cpp | 380 | ||||
-rw-r--r-- | src/net/maildir/maildirMessage.cpp | 2 | ||||
-rw-r--r-- | src/net/maildir/maildirUtils.cpp | 32 | ||||
-rw-r--r-- | src/net/messageSet.cpp | 369 | ||||
-rw-r--r-- | src/net/pop3/POP3Folder.cpp | 173 | ||||
-rw-r--r-- | src/net/pop3/POP3Utils.cpp | 32 | ||||
-rw-r--r-- | tests/net/messageSetTest.cpp | 200 | ||||
-rw-r--r-- | vmime/net/folder.hpp | 101 | ||||
-rw-r--r-- | vmime/net/imap/IMAPFolder.hpp | 17 | ||||
-rw-r--r-- | vmime/net/imap/IMAPUtils.hpp | 54 | ||||
-rw-r--r-- | vmime/net/maildir/maildirFolder.hpp | 17 | ||||
-rw-r--r-- | vmime/net/maildir/maildirUtils.hpp | 9 | ||||
-rw-r--r-- | vmime/net/messageSet.hpp | 335 | ||||
-rw-r--r-- | vmime/net/pop3/POP3Folder.hpp | 17 | ||||
-rw-r--r-- | vmime/net/pop3/POP3Utils.hpp | 9 |
20 files changed, 1360 insertions, 982 deletions
@@ -206,6 +206,7 @@ libvmime_messaging_sources = [ 'net/folder.cpp', 'net/folder.hpp', 'net/folderStatus.hpp', 'net/message.cpp', 'net/message.hpp', + 'net/messageSet.cpp', 'net/messageSet.hpp', 'net/securedConnectionInfos.hpp', 'net/service.cpp', 'net/service.hpp', 'net/serviceFactory.cpp', 'net/serviceFactory.hpp', @@ -412,6 +413,7 @@ libvmimetest_sources = [ 'tests/security/digest/md5Test.cpp', 'tests/security/digest/sha1Test.cpp', # =============================== Net ================================ + 'tests/net/messageSetTest.cpp', 'tests/net/pop3/POP3CommandTest.cpp', 'tests/net/pop3/POP3ResponseTest.cpp', 'tests/net/pop3/POP3UtilsTest.cpp', diff --git a/doc/book/net.tex b/doc/book/net.tex index 19a6ccb9..7359c3e3 100644 --- a/doc/book/net.tex +++ b/doc/book/net.tex @@ -555,7 +555,9 @@ The following code shows how to list all the messages in a folder, and retrieve basic information to show them to the user: \begin{lstlisting}[caption={Fetching information about multiple messages}] -std::vector <ref <vmime::net::message> > allMessages = folder->getMessages(); +std::vector <ref <vmime::net::message> > allMessages = + folder->getMessages(vmime::net::messageSet::byNumber(1, -1)); + // -1 is a special value to mean "the number of the last message in the folder" folder->fetchMessages(allMessages, vmime::net::folder::FETCH_FLAGS | @@ -628,17 +630,17 @@ store. \begin{lstlisting}[caption={Deleting messages}] vmime::ref <vmime::net::folder> folder = store->getDefaultFolder(); -folder->deleteMessage(3); -folder->deleteMessage(2); +folder->deleteMessages(vmime::net::messageSet::byNumber(/* from */ 2, /* to */ 3)); // This is equivalent std::vector <int> nums; nums.push_back(2); nums.push_back(3); -folder->deleteMessages(nums); +folder->deleteMessages(vmime::net::messageSet::byNumber(nums)); -// This is also equivalent -folder->deleteMessages(/* from */ 2, /* to */ 3); +// This is also equivalent (but will require 2 roundtrips to server) +folder->deleteMessages(vmime::net::messageSet::byNumber(2)); +folder->deleteMessages(vmime::net::messageSet::byNumber(2)); // renumbered, 3 becomes 2 \end{lstlisting} \subsection{Events} % -------------------------------------------------------- diff --git a/src/net/imap/IMAPFolder.cpp b/src/net/imap/IMAPFolder.cpp index 3a38182e..dd7f2512 100644 --- a/src/net/imap/IMAPFolder.cpp +++ b/src/net/imap/IMAPFolder.cpp @@ -529,135 +529,93 @@ ref <message> IMAPFolder::getMessage(const int num) } -std::vector <ref <message> > IMAPFolder::getMessages(const int from, const int to) +std::vector <ref <message> > IMAPFolder::getMessages(const messageSet& msgs) { - const int messageCount = m_status->getMessageCount(); - const int to2 = (to == -1 ? messageCount : to); - if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - else if (to2 < from || from < 1 || to2 < 1 || from > messageCount || to2 > messageCount) - throw exceptions::message_not_found(); - - std::vector <ref <message> > v; - ref <IMAPFolder> thisFolder = thisRef().dynamicCast <IMAPFolder>(); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create <IMAPMessage>(thisFolder, i)); - - return (v); -} - - -std::vector <ref <message> > IMAPFolder::getMessages(const std::vector <int>& nums) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - std::vector <ref <message> > v; - ref <IMAPFolder> thisFolder = thisRef().dynamicCast <IMAPFolder>(); - - for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - v.push_back(vmime::create <IMAPMessage>(thisFolder, *it)); - - return (v); -} - - -ref <message> IMAPFolder::getMessageByUID(const message::uid& uid) -{ - std::vector <message::uid> uids; - uids.push_back(uid); - - std::vector <ref <message> > msgs = getMessagesByUID(uids); - - if (msgs.size() == 0) - throw exceptions::message_not_found(); - - return msgs[0]; -} - -std::vector <ref <message> > IMAPFolder::getMessagesByUID(const std::vector <message::uid>& uids) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - if (uids.size() == 0) + if (msgs.isEmpty() == 0) return std::vector <ref <message> >(); - // C: . UID FETCH uuuu1,uuuu2,uuuu3 UID - // S: * nnnn1 FETCH (UID uuuu1) - // S: * nnnn2 FETCH (UID uuuu2) - // S: * nnnn3 FETCH (UID uuuu3) - // S: . OK UID FETCH completed + std::vector <ref <message> > messages; - // Prepare command and arguments - std::ostringstream cmd; - cmd.imbue(std::locale::classic()); + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = IMAPUtils::messageSetToNumberList(msgs); - cmd << "UID FETCH " << uids[0]; + ref <IMAPFolder> thisFolder = thisRef().dynamicCast <IMAPFolder>(); - for (std::vector <message::uid>::size_type i = 1, n = uids.size() ; i < n ; ++i) - cmd << "," << uids[i]; + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + messages.push_back(vmime::create <IMAPMessage>(thisFolder, *it)); + } + else if (msgs.isUIDSet()) + { + // C: . UID FETCH uuuu1,uuuu2,uuuu3 UID + // S: * nnnn1 FETCH (UID uuuu1) + // S: * nnnn2 FETCH (UID uuuu2) + // S: * nnnn3 FETCH (UID uuuu3) + // S: . OK UID FETCH completed - cmd << " UID"; + // Prepare command and arguments + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); - // Send the request - m_connection->send(true, cmd.str(), true); + cmd << "UID FETCH " << IMAPUtils::messageSetToSequenceSet(msgs) << " UID"; - // Get the response - utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); + // Send the request + m_connection->send(true, cmd.str(), true); - if (resp->isBad() || resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) - { - throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "bad response"); - } + // Get the response + utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); - // Process the response - const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = - resp->continue_req_or_response_data(); + if (resp->isBad() || resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "bad response"); + } - std::vector <ref <message> > messages; + // Process the response + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); - for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator - it = respDataList.begin() ; it != respDataList.end() ; ++it) - { - if ((*it)->response_data() == NULL) + for (std::vector <IMAPParser::continue_req_or_response_data*>::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) { - throw exceptions::command_error("UID FETCH ... UID", - resp->getErrorLog(), "invalid response"); - } + if ((*it)->response_data() == NULL) + { + throw exceptions::command_error("UID FETCH ... UID", + resp->getErrorLog(), "invalid response"); + } - const IMAPParser::message_data* messageData = - (*it)->response_data()->message_data(); + const IMAPParser::message_data* messageData = + (*it)->response_data()->message_data(); - // We are only interested in responses of type "FETCH" - if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH) - continue; + // We are only interested in responses of type "FETCH" + if (messageData == NULL || messageData->type() != IMAPParser::message_data::FETCH) + continue; - // Get Process fetch response for this message - const int msgNum = static_cast <int>(messageData->number()); - message::uid msgUID; + // Get Process fetch response for this message + const int msgNum = static_cast <int>(messageData->number()); + message::uid msgUID; - // Find UID in message attributes - const std::vector <IMAPParser::msg_att_item*> atts = messageData->msg_att()->items(); + // Find UID in message attributes + const std::vector <IMAPParser::msg_att_item*> atts = messageData->msg_att()->items(); - for (std::vector <IMAPParser::msg_att_item*>::const_iterator - it = atts.begin() ; it != atts.end() ; ++it) - { - if ((*it)->type() == IMAPParser::msg_att_item::UID) + for (std::vector <IMAPParser::msg_att_item*>::const_iterator + it = atts.begin() ; it != atts.end() ; ++it) { - msgUID = (*it)->unique_id()->value(); - break; + if ((*it)->type() == IMAPParser::msg_att_item::UID) + { + msgUID = (*it)->unique_id()->value(); + break; + } } - } - if (!msgUID.empty()) - { - ref <IMAPFolder> thisFolder = thisRef().dynamicCast <IMAPFolder>(); - messages.push_back(vmime::create <IMAPMessage>(thisFolder, msgNum, msgUID)); + if (!msgUID.empty()) + { + ref <IMAPFolder> thisFolder = thisRef().dynamicCast <IMAPFolder>(); + messages.push_back(vmime::create <IMAPMessage>(thisFolder, msgNum, msgUID)); + } } } @@ -816,7 +774,9 @@ void IMAPFolder::fetchMessages(std::vector <ref <message> >& msg, const int opti } // Send the request - const string command = IMAPUtils::buildFetchRequest(m_connection, list, options); + const string command = IMAPUtils::buildFetchRequest + (m_connection, messageSet::byNumber(list), options); + m_connection->send(true, command, true); // Get the response @@ -945,17 +905,11 @@ void IMAPFolder::onStoreDisconnected() } -void IMAPFolder::deleteMessage(const int num) -{ - deleteMessages(num, num); -} - - -void IMAPFolder::deleteMessages(const int from, const int to) +void IMAPFolder::deleteMessages(const messageSet& msgs) { ref <IMAPStore> store = m_store.acquire(); - if (from < 1 || (to < from && to != -1)) + if (msgs.isEmpty()) throw exceptions::invalid_argument(); if (!store) @@ -969,19 +923,10 @@ void IMAPFolder::deleteMessages(const int from, const int to) std::ostringstream command; command.imbue(std::locale::classic()); - command << "STORE "; - - if (from == to) - { - command << from; - } + if (msgs.isUIDSet()) + command << "UID STORE" << IMAPUtils::messageSetToSequenceSet(msgs); else - { - command << from << ":"; - - if (to == -1) command << m_status->getMessageCount(); - else command << to; - } + command << "STORE" << IMAPUtils::messageSetToSequenceSet(msgs); command << " +FLAGS (\\Deleted)"; @@ -1002,116 +947,16 @@ void IMAPFolder::deleteMessages(const int from, const int to) } -void IMAPFolder::deleteMessages(const std::vector <int>& nums) +void IMAPFolder::setMessageFlags(const messageSet& msgs, const int flags, const int mode) { - ref <IMAPStore> store = m_store.acquire(); - - if (nums.empty()) - throw exceptions::invalid_argument(); - - if (!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 <int> list; - - list.resize(nums.size()); - std::copy(nums.begin(), nums.end(), list.begin()); - - std::sort(list.begin(), list.end()); - // Build the request text std::ostringstream command; command.imbue(std::locale::classic()); - command << "STORE "; - command << IMAPUtils::listToSet(list, m_status->getMessageCount(), true); - command << " +FLAGS (\\Deleted)"; - - // Send the request - m_connection->send(true, command.str(), true); - - // Get the response - utility::auto_ptr <IMAPParser::response> resp(m_connection->readResponse()); - - if (resp->isBad() || resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) - { - throw exceptions::command_error("STORE", - resp->getErrorLog(), "bad response"); - } - - processStatusUpdate(resp); -} - - -void IMAPFolder::setMessageFlags(const int from, const int to, const int flags, const int mode) -{ - ref <IMAPStore> store = m_store.acquire(); - - if (from < 1 || (to < from && to != -1)) - throw exceptions::invalid_argument(); - - if (!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"); - - std::ostringstream oss; - oss.imbue(std::locale::classic()); - - if (from == to) - { - oss << from; - } + if (msgs.isUIDSet()) + command << "UID STORE " << IMAPUtils::messageSetToSequenceSet(msgs); else - { - if (to == -1) - oss << from << ":*"; - else - oss << from << ":" << to; - } - - setMessageFlagsImpl(oss.str(), flags, mode); -} - - -void IMAPFolder::setMessageFlags(const std::vector <int>& nums, const int flags, const int mode) -{ - ref <IMAPStore> store = m_store.acquire(); - - if (!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 <int> list; - - list.resize(nums.size()); - std::copy(nums.begin(), nums.end(), list.begin()); - std::sort(list.begin(), list.end()); - - // Delegates call - setMessageFlagsImpl(IMAPUtils::listToSet(list, m_status->getMessageCount(), true), flags, mode); -} - - -void IMAPFolder::setMessageFlagsImpl(const string& set, const int flags, const int mode) -{ - // Build the request text - std::ostringstream command; - command.imbue(std::locale::classic()); - - command << "STORE " << set; + command << "STORE " << IMAPUtils::messageSetToSequenceSet(msgs); switch (mode) { @@ -1364,27 +1209,7 @@ void IMAPFolder::rename(const folder::path& newPath) } -void IMAPFolder::copyMessage(const folder::path& dest, const int num) -{ - ref <IMAPStore> store = m_store.acquire(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - // Construct set - std::ostringstream set; - set.imbue(std::locale::classic()); - - set << num; - - // Delegate message copy - copyMessagesImpl(set.str(), dest); -} - - -void IMAPFolder::copyMessages(const folder::path& dest, const int from, const int to) +void IMAPFolder::copyMessages(const folder::path& dest, const messageSet& set) { ref <IMAPStore> store = m_store.acquire(); @@ -1392,44 +1217,12 @@ void IMAPFolder::copyMessages(const folder::path& dest, const int from, const in throw exceptions::illegal_state("Store disconnected"); else if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - else if (from < 1 || (to < from && to != -1)) - throw exceptions::invalid_argument(); - - // Construct set - std::ostringstream set; - set.imbue(std::locale::classic()); - if (to == -1) - set << from << ":*"; - else - set << from << ":" << to; - - // Delegate message copy - copyMessagesImpl(set.str(), dest); -} - - -void IMAPFolder::copyMessages(const folder::path& dest, const std::vector <int>& nums) -{ - ref <IMAPStore> store = m_store.acquire(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - // Delegate message copy - copyMessagesImpl(IMAPUtils::listToSet(nums, m_status->getMessageCount()), dest); -} - - -void IMAPFolder::copyMessagesImpl(const string& set, const folder::path& dest) -{ // Build the request text std::ostringstream command; command.imbue(std::locale::classic()); - command << "COPY " << set << " "; + command << "COPY " << IMAPUtils::messageSetToSequenceSet(set) << " "; command << IMAPUtils::quoteString(IMAPUtils::pathToString (m_connection->hierarchySeparator(), dest)); @@ -1599,6 +1392,8 @@ std::vector <int> IMAPFolder::getMessageNumbersStartingOnUID(const message::uid& } } + processStatusUpdate(resp); + return v; } diff --git a/src/net/imap/IMAPMessage.cpp b/src/net/imap/IMAPMessage.cpp index 96114e94..33599689 100644 --- a/src/net/imap/IMAPMessage.cpp +++ b/src/net/imap/IMAPMessage.cpp @@ -531,7 +531,10 @@ void IMAPMessage::setFlags(const int flags, const int mode) if (!folder) throw exceptions::folder_not_found(); - folder->setMessageFlags(m_num, m_num, flags, mode); + if (!m_uid.empty()) + folder->setMessageFlags(messageSet::byUID(m_uid), flags, mode); + else + folder->setMessageFlags(messageSet::byNumber(m_num), flags, mode); } diff --git a/src/net/imap/IMAPUtils.cpp b/src/net/imap/IMAPUtils.cpp index dc583f84..b5f8b38d 100644 --- a/src/net/imap/IMAPUtils.cpp +++ b/src/net/imap/IMAPUtils.cpp @@ -472,98 +472,6 @@ const string IMAPUtils::messageFlagList(const int flags) // static -const string IMAPUtils::listToSet(const std::vector <int>& list, const int max, - const bool alreadySorted) -{ - // Sort a copy of the list (if not already sorted) - std::vector <int> temp; - - if (!alreadySorted) - { - temp.resize(list.size()); - std::copy(list.begin(), list.end(), temp.begin()); - - std::sort(temp.begin(), temp.end()); - } - - const std::vector <int>& theList = (alreadySorted ? list : temp); - - // Build the set - std::ostringstream res; - res.imbue(std::locale::classic()); - - int previous = -1, setBegin = -1; - - for (std::vector <int>::const_iterator it = theList.begin() ; - it != theList.end() ; ++it) - { - const int current = *it; - - if (previous == -1) - { - res << current; - - previous = current; - setBegin = current; - } - else - { - if (current == previous + 1) - { - previous = current; - } - else - { - if (setBegin != previous) - { - res << ":" << previous << "," << current; - - previous = current; - setBegin = current; - } - else - { - if (setBegin != current) // skip duplicates - res << "," << current; - - previous = current; - setBegin = current; - } - } - } - } - - if (previous != setBegin) - { - if (previous == max) - res << ":*"; - else - res << ":" << previous; - } - - return (res.str()); -} - - -// static -const string IMAPUtils::listToSet(const std::vector <message::uid>& list) -{ - if (list.size() == 0) - return ""; - - std::ostringstream res; - res.imbue(std::locale::classic()); - - res << list[0]; - - for (std::vector <message::uid>::size_type i = 1, n = list.size() ; i < n ; ++i) - res << "," << list[i]; - - return res.str(); -} - - -// static const string IMAPUtils::dateTime(const vmime::datetime& date) { std::ostringstream res; @@ -633,8 +541,8 @@ const string IMAPUtils::dateTime(const vmime::datetime& date) // static -const string IMAPUtils::buildFetchRequestImpl - (ref <IMAPConnection> cnt, const string& mode, const string& set, const int options) +const string IMAPUtils::buildFetchRequest + (ref <IMAPConnection> cnt, const messageSet& msgs, const int options) { // Example: // C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) @@ -702,10 +610,10 @@ const string IMAPUtils::buildFetchRequestImpl std::ostringstream command; command.imbue(std::locale::classic()); - if (mode == "uid") - command << "UID FETCH " << set << " ("; + if (msgs.isUIDSet()) + command << "UID FETCH " << messageSetToSequenceSet(msgs) << " ("; else - command << "FETCH " << set << " ("; + command << "FETCH " << messageSetToSequenceSet(msgs) << " ("; for (std::vector <string>::const_iterator it = items.begin() ; it != items.end() ; ++it) @@ -721,22 +629,6 @@ const string IMAPUtils::buildFetchRequestImpl // static -const string IMAPUtils::buildFetchRequest - (ref <IMAPConnection> cnt, const std::vector <int>& list, const int options) -{ - return buildFetchRequestImpl(cnt, "number", listToSet(list, -1, false), options); -} - - -// static -const string IMAPUtils::buildFetchRequest - (ref <IMAPConnection> cnt, const std::vector <message::uid>& list, const int options) -{ - return buildFetchRequestImpl(cnt, "uid", listToSet(list), options); -} - - -// static void IMAPUtils::convertAddressList (const IMAPParser::address_list& src, mailboxList& dest) { @@ -756,6 +648,101 @@ void IMAPUtils::convertAddressList } + +class IMAPUIDMessageSetEnumerator : public messageSetEnumerator +{ +public: + + IMAPUIDMessageSetEnumerator() + : m_first(true) + { + } + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + const std::string str() const + { + return m_oss.str(); + } + +private: + + std::ostringstream m_oss; + bool m_first; +}; + + +class IMAPMessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + m_list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not used + } + + const std::vector <int>& list() const + { + return m_list; + } + +public: + + std::vector <int> m_list; +}; + + + +// static +const string IMAPUtils::messageSetToSequenceSet(const messageSet& msgs) +{ + IMAPUIDMessageSetEnumerator en; + msgs.enumerate(en); + + return en.str(); +} + + +// static +const std::vector <int> IMAPUtils::messageSetToNumberList(const messageSet& msgs) +{ + IMAPMessageSetEnumerator en; + msgs.enumerate(en); + + return en.list(); +} + + } // imap } // net } // vmime diff --git a/src/net/maildir/maildirFolder.cpp b/src/net/maildir/maildirFolder.cpp index 3042d4b5..42a2c5ff 100644 --- a/src/net/maildir/maildirFolder.cpp +++ b/src/net/maildir/maildirFolder.cpp @@ -419,49 +419,32 @@ ref <message> maildirFolder::getMessage(const int num) } -std::vector <ref <message> > maildirFolder::getMessages(const int from, const int to) -{ - const int to2 = (to == -1 ? m_messageCount : to); - - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount) - throw exceptions::message_not_found(); - - std::vector <ref <message> > v; - ref <maildirFolder> thisFolder = thisRef().dynamicCast <maildirFolder>(); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create <maildirMessage>(thisFolder, i)); - - return (v); -} - - -std::vector <ref <message> > maildirFolder::getMessages(const std::vector <int>& nums) +std::vector <ref <message> > maildirFolder::getMessages(const messageSet& msgs) { if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - std::vector <ref <message> > v; - ref <maildirFolder> thisFolder = thisRef().dynamicCast <maildirFolder>(); - - for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - v.push_back(vmime::create <maildirMessage>(thisFolder, *it)); - - return (v); -} + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = maildirUtils::messageSetToNumberList(msgs); + std::vector <ref <message> > messages; + ref <maildirFolder> thisFolder = thisRef().dynamicCast <maildirFolder>(); -ref <message> maildirFolder::getMessageByUID(const message::uid& /* uid */) -{ - throw exceptions::operation_not_supported(); -} + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); + messages.push_back(vmime::create <maildirMessage>(thisFolder, *it)); + } -std::vector <ref <message> > maildirFolder::getMessagesByUID(const std::vector <message::uid>& /* uids */) -{ - throw exceptions::operation_not_supported(); + return messages; + } + else + { + throw exceptions::operation_not_supported(); + } } @@ -590,35 +573,18 @@ void maildirFolder::rename(const folder::path& newPath) } -void maildirFolder::deleteMessage(const int num) -{ - // Mark messages as deleted - setMessageFlags(num, num, message::FLAG_DELETED, message::FLAG_MODE_ADD); -} - - -void maildirFolder::deleteMessages(const int from, const int to) -{ - // Mark messages as deleted - setMessageFlags(from, to, message::FLAG_DELETED, message::FLAG_MODE_ADD); -} - - -void maildirFolder::deleteMessages(const std::vector <int>& nums) +void maildirFolder::deleteMessages(const messageSet& msgs) { // Mark messages as deleted - setMessageFlags(nums, message::FLAG_DELETED, message::FLAG_MODE_ADD); + setMessageFlags(msgs, message::FLAG_DELETED, message::FLAG_MODE_ADD); } void maildirFolder::setMessageFlags - (const int from, const int to, const int flags, const int mode) + (const messageSet& msgs, const int flags, const int mode) { ref <maildirStore> store = m_store.acquire(); - if (from < 1 || (to < from && to != -1)) - throw exceptions::invalid_argument(); - if (!store) throw exceptions::illegal_state("Store disconnected"); else if (!isOpen()) @@ -626,210 +592,116 @@ void maildirFolder::setMessageFlags else if (m_mode == MODE_READ_ONLY) throw exceptions::illegal_state("Folder is read-only"); - // Construct the list of message numbers - const int to2 = (to == -1) ? m_messageCount : to; - const int count = to - from + 1; - - std::vector <int> nums; - nums.resize(count); + if (msgs.isNumberSet()) + { + const std::vector <int> nums = maildirUtils::messageSetToNumberList(msgs); - for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) - nums[j] = i; + // Change message flags + ref <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); - // Change message flags - setMessageFlagsImpl(nums, flags, mode); + utility::file::path curDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); - // Update local flags - switch (mode) - { - case message::FLAG_MODE_ADD: - { - for (std::vector <maildirMessage*>::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 |= flags; - } - } - - break; - } - case message::FLAG_MODE_REMOVE: - { - for (std::vector <maildirMessage*>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + for (std::vector <int>::const_iterator it = + nums.begin() ; it != nums.end() ; ++it) { - if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && - (*it)->m_flags != message::FLAG_UNDEFINED) - { - (*it)->m_flags &= ~flags; - } - } + const int num = *it - 1; - break; - } - default: - case message::FLAG_MODE_SET: - { - for (std::vector <maildirMessage*>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 && - (*it)->m_flags != message::FLAG_UNDEFINED) + try { - (*it)->m_flags = flags; - } - } - - break; - } - - } - - // Notify message flags changed - ref <events::messageChangedEvent> event = - vmime::create <events::messageChangedEvent> - (thisRef().dynamicCast <folder>(), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); - - // TODO: notify other folders with the same path -} - + const utility::file::path::component path = m_messageInfos[num].path; + ref <utility::file> file = fsf->create(curDirPath / path); -void maildirFolder::setMessageFlags - (const std::vector <int>& nums, const int flags, const int mode) -{ - ref <maildirStore> store = m_store.acquire(); - - if (!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"); + int newFlags = maildirUtils::extractFlags(path); - // Sort the list of message numbers - std::vector <int> list; + switch (mode) + { + case message::FLAG_MODE_ADD: newFlags |= flags; break; + case message::FLAG_MODE_REMOVE: newFlags &= ~flags; break; + default: + case message::FLAG_MODE_SET: newFlags = flags; break; + } - list.resize(nums.size()); - std::copy(nums.begin(), nums.end(), list.begin()); + const utility::file::path::component newPath = maildirUtils::buildFilename + (maildirUtils::extractId(path), newFlags); - std::sort(list.begin(), list.end()); + file->rename(curDirPath / newPath); - // Change message flags - setMessageFlagsImpl(list, flags, mode); + if (flags & message::FLAG_DELETED) + m_messageInfos[num].type = messageInfos::TYPE_DELETED; + else + m_messageInfos[num].type = messageInfos::TYPE_CUR; - // Update local flags - switch (mode) - { - case message::FLAG_MODE_ADD: - { - for (std::vector <maildirMessage*>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) + m_messageInfos[num].path = newPath; + } + catch (exceptions::filesystem_exception& e) { - (*it)->m_flags |= flags; + // Ignore (not important) } } - break; - } - case message::FLAG_MODE_REMOVE: - { - for (std::vector <maildirMessage*>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + // Update local flags + switch (mode) + { + case message::FLAG_MODE_ADD: { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) + for (std::vector <maildirMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) { - (*it)->m_flags &= ~flags; + if (std::binary_search(nums.begin(), nums.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags |= flags; + } } - } - break; - } - default: - case message::FLAG_MODE_SET: - { - for (std::vector <maildirMessage*>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + break; + } + case message::FLAG_MODE_REMOVE: { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) + for (std::vector <maildirMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) { - (*it)->m_flags = flags; + if (std::binary_search(nums.begin(), nums.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags &= ~flags; + } } - } - - break; - } - - } - - // Notify message flags changed - ref <events::messageChangedEvent> event = - vmime::create <events::messageChangedEvent> - (thisRef().dynamicCast <folder>(), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); - // TODO: notify other folders with the same path -} - - -void maildirFolder::setMessageFlagsImpl - (const std::vector <int>& nums, const int flags, const int mode) -{ - ref <maildirStore> store = m_store.acquire(); - - ref <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); - - utility::file::path curDirPath = store->getFormat()-> - folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); - - for (std::vector <int>::const_iterator it = - nums.begin() ; it != nums.end() ; ++it) - { - const int num = *it - 1; - - try + break; + } + default: + case message::FLAG_MODE_SET: { - const utility::file::path::component path = m_messageInfos[num].path; - ref <utility::file> file = fsf->create(curDirPath / path); - - int newFlags = maildirUtils::extractFlags(path); - - switch (mode) + for (std::vector <maildirMessage*>::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) { - case message::FLAG_MODE_ADD: newFlags |= flags; break; - case message::FLAG_MODE_REMOVE: newFlags &= ~flags; break; - default: - case message::FLAG_MODE_SET: newFlags = flags; break; + if (std::binary_search(nums.begin(), nums.end(), (*it)->getNumber()) && + (*it)->m_flags != message::FLAG_UNDEFINED) + { + (*it)->m_flags = flags; + } } - const utility::file::path::component newPath = maildirUtils::buildFilename - (maildirUtils::extractId(path), newFlags); + break; + } - file->rename(curDirPath / newPath); + } - if (flags & message::FLAG_DELETED) - m_messageInfos[num].type = messageInfos::TYPE_DELETED; - else - m_messageInfos[num].type = messageInfos::TYPE_CUR; + // Notify message flags changed + ref <events::messageChangedEvent> event = + vmime::create <events::messageChangedEvent> + (thisRef().dynamicCast <folder>(), + events::messageChangedEvent::TYPE_FLAGS, nums); - m_messageInfos[num].path = newPath; - } - catch (exceptions::filesystem_exception& e) - { - // Ignore (not important) - } + notifyMessageChanged(event); + + // TODO: notify other folders with the same path + } + else + { + throw exceptions::operation_not_supported(); } } @@ -1032,46 +904,7 @@ void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath, } -void maildirFolder::copyMessage(const folder::path& dest, const int num) -{ - ref <maildirStore> store = m_store.acquire(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - copyMessages(dest, num, num); -} - - -void maildirFolder::copyMessages(const folder::path& dest, const int from, const int to) -{ - ref <maildirStore> store = m_store.acquire(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - else if (from < 1 || (to < from && to != -1)) - throw exceptions::invalid_argument(); - - // Construct the list of message numbers - const int to2 = (to == -1) ? m_messageCount : to; - const int count = to - from + 1; - - std::vector <int> nums; - nums.resize(count); - - for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) - nums[j] = i; - - // Copy messages - copyMessagesImpl(dest, nums); -} - - -void maildirFolder::copyMessages(const folder::path& dest, const std::vector <int>& nums) +void maildirFolder::copyMessages(const folder::path& dest, const messageSet& msgs) { ref <maildirStore> store = m_store.acquire(); @@ -1080,15 +913,6 @@ void maildirFolder::copyMessages(const folder::path& dest, const std::vector <in else if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - // Copy messages - copyMessagesImpl(dest, nums); -} - - -void maildirFolder::copyMessagesImpl(const folder::path& dest, const std::vector <int>& nums) -{ - ref <maildirStore> store = m_store.acquire(); - ref <utility::fileSystemFactory> fsf = platform::getHandler()->getFileSystemFactory(); utility::file::path curDirPath = store->getFormat()->folderPathToFileSystemPath @@ -1121,6 +945,8 @@ void maildirFolder::copyMessagesImpl(const folder::path& dest, const std::vector } // Copy messages + const std::vector <int> nums = maildirUtils::messageSetToNumberList(msgs); + try { for (std::vector <int>::const_iterator it = diff --git a/src/net/maildir/maildirMessage.cpp b/src/net/maildir/maildirMessage.cpp index e63d5edf..a7c2a22f 100644 --- a/src/net/maildir/maildirMessage.cpp +++ b/src/net/maildir/maildirMessage.cpp @@ -140,7 +140,7 @@ void maildirMessage::setFlags(const int flags, const int mode) if (!folder) throw exceptions::folder_not_found(); - folder->setMessageFlags(m_num, m_num, flags, mode); + folder->setMessageFlags(messageSet::byNumber(m_num), flags, mode); } diff --git a/src/net/maildir/maildirUtils.cpp b/src/net/maildir/maildirUtils.cpp index 9028fa59..c4ba2857 100644 --- a/src/net/maildir/maildirUtils.cpp +++ b/src/net/maildir/maildirUtils.cpp @@ -214,6 +214,38 @@ void maildirUtils::recursiveFSDelete(ref <utility::file> dir) +class maildirMessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not supported + } + +public: + + std::vector <int> list; +}; + + +// static +const std::vector <int> maildirUtils::messageSetToNumberList(const messageSet& msgs) +{ + maildirMessageSetEnumerator en; + msgs.enumerate(en); + + return en.list; +} + + + // // messageIdComparator // diff --git a/src/net/messageSet.cpp b/src/net/messageSet.cpp new file mode 100644 index 00000000..04f1debb --- /dev/null +++ b/src/net/messageSet.cpp @@ -0,0 +1,369 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/net/messageSet.hpp" + +#include <iterator> +#include <algorithm> +#include <typeinfo> + + +namespace vmime { +namespace net { + + +// messageRange + +messageRange::messageRange() +{ +} + + +messageRange::~messageRange() +{ +} + + +// numberMessageRange + +numberMessageRange::numberMessageRange(const int number) + : m_first(number), m_last(number) +{ + if (number < 1) + throw std::invalid_argument("number"); +} + + +numberMessageRange::numberMessageRange(const int first, const int last) + : m_first(first), m_last(last) +{ + if (first < 1) + throw std::invalid_argument("first"); + else if (last != -1 && last < first) + throw std::invalid_argument("last"); +} + + +numberMessageRange::numberMessageRange(const numberMessageRange& other) + : messageRange(), m_first(other.m_first), m_last(other.m_last) +{ +} + + +int numberMessageRange::getFirst() const +{ + return m_first; +} + + +int numberMessageRange::getLast() const +{ + return m_last; +} + + +void numberMessageRange::enumerate(messageSetEnumerator& en) const +{ + en.enumerateNumberMessageRange(*this); +} + + +messageRange* numberMessageRange::clone() const +{ + return new numberMessageRange(*this); +} + + +// UIDMessageRange + +UIDMessageRange::UIDMessageRange(const message::uid& uid) + : m_first(uid), m_last(uid) +{ +} + + +UIDMessageRange::UIDMessageRange(const message::uid& first, const message::uid& last) + : m_first(first), m_last(last) +{ +} + + +UIDMessageRange::UIDMessageRange(const UIDMessageRange& other) + : messageRange(), m_first(other.m_first), m_last(other.m_last) +{ +} + + +const message::uid UIDMessageRange::getFirst() const +{ + return m_first; +} + + +const message::uid UIDMessageRange::getLast() const +{ + return m_last; +} + + +void UIDMessageRange::enumerate(messageSetEnumerator& en) const +{ + en.enumerateUIDMessageRange(*this); +} + + +messageRange* UIDMessageRange::clone() const +{ + return new UIDMessageRange(*this); +} + + +// messageSet + + +messageSet::messageSet() +{ +} + + +messageSet::messageSet(const messageSet& other) + : object() +{ + m_ranges.resize(other.m_ranges.size()); + + for (unsigned int i = 0, n = other.m_ranges.size() ; i < n ; ++i) + m_ranges[i] = other.m_ranges[i]->clone(); +} + + +messageSet::~messageSet() +{ + for (unsigned int i = 0, n = m_ranges.size() ; i < n ; ++i) + delete m_ranges[i]; +} + + +// static +messageSet messageSet::byNumber(const int number) +{ + messageSet set; + set.m_ranges.push_back(new numberMessageRange(number)); + + return set; +} + + +// static +messageSet messageSet::byNumber(const int first, const int last) +{ + messageSet set; + set.m_ranges.push_back(new numberMessageRange(first, last)); + + return set; +} + + +// static +messageSet messageSet::byNumber(const std::vector <int>& numbers) +{ + // Sort a copy of the list + std::vector <int> sortedNumbers; + + sortedNumbers.resize(numbers.size()); + + std::copy(numbers.begin(), numbers.end(), sortedNumbers.begin()); + std::sort(sortedNumbers.begin(), sortedNumbers.end()); + + // Build the set by detecting ranges of continuous numbers + int previous = -1, rangeStart = -1; + messageSet set; + + for (std::vector <int>::const_iterator it = sortedNumbers.begin() ; + it != sortedNumbers.end() ; ++it) + { + const int current = *it; + + if (current == previous) + continue; // skip duplicates + + if (previous == -1) + { + previous = current; + rangeStart = current; + } + else + { + if (current == previous + 1) + { + previous = current; + } + else + { + set.m_ranges.push_back(new numberMessageRange(rangeStart, previous)); + + previous = current; + rangeStart = current; + } + } + } + + set.m_ranges.push_back(new numberMessageRange(rangeStart, previous)); + + return set; +} + + +// static +messageSet messageSet::byUID(const message::uid& uid) +{ + messageSet set; + set.m_ranges.push_back(new UIDMessageRange(uid)); + + return set; +} + + +messageSet messageSet::byUID(const message::uid& first, const message::uid& last) +{ + messageSet set; + set.m_ranges.push_back(new UIDMessageRange(first, last)); + + return set; +} + + +messageSet messageSet::byUID(const std::vector <message::uid>& uids) +{ + std::vector <vmime_uint32> numericUIDs; + + for (unsigned int i = 0, n = uids.size() ; i < n ; ++i) + { + const string uid = uids[i]; + int numericUID = 0; + + const string::value_type* p = uid.c_str(); + + for ( ; *p >= '0' && *p <= '9' ; ++p) + numericUID = (numericUID * 10) + (*p - '0'); + + if (*p != '\0') + { + messageSet set; + + // Non-numeric UID, fall back to plain UID list (single-UID ranges) + for (unsigned int i = 0, n = uids.size() ; i < n ; ++i) + set.m_ranges.push_back(new UIDMessageRange(uids[i])); + + return set; + } + + numericUIDs.push_back(numericUID); + } + + // Sort a copy of the list + std::vector <vmime_uint32> sortedUIDs; + + sortedUIDs.resize(numericUIDs.size()); + + std::copy(numericUIDs.begin(), numericUIDs.end(), sortedUIDs.begin()); + std::sort(sortedUIDs.begin(), sortedUIDs.end()); + + // Build the set by detecting ranges of continuous numbers + vmime_uint32 previous = -1U, rangeStart = -1U; + messageSet set; + + for (std::vector <vmime_uint32>::const_iterator it = sortedUIDs.begin() ; + it != sortedUIDs.end() ; ++it) + { + const vmime_uint32 current = *it; + + if (current == previous) + continue; // skip duplicates + + if (previous == -1U) + { + previous = current; + rangeStart = current; + } + else + { + if (current == previous + 1) + { + previous = current; + } + else + { + set.m_ranges.push_back(new UIDMessageRange + (static_cast <std::ostringstream*>(&(std::ostringstream() << rangeStart))->str(), + static_cast <std::ostringstream*>(&(std::ostringstream() << previous))->str())); + + previous = current; + rangeStart = current; + } + } + } + + set.m_ranges.push_back(new UIDMessageRange + (static_cast <std::ostringstream*>(&(std::ostringstream() << rangeStart))->str(), + static_cast <std::ostringstream*>(&(std::ostringstream() << previous))->str())); + + return set; +} + + +void messageSet::addRange(const messageRange& range) +{ + if (!m_ranges.empty() && typeid(*m_ranges[0]) != typeid(range)) + throw std::invalid_argument("range"); + + m_ranges.push_back(range.clone()); +} + + +void messageSet::enumerate(messageSetEnumerator& en) const +{ + for (unsigned int i = 0, n = m_ranges.size() ; i < n ; ++i) + m_ranges[i]->enumerate(en); +} + + +bool messageSet::isEmpty() const +{ + return m_ranges.empty(); +} + + +bool messageSet::isNumberSet() const +{ + return !isEmpty() && dynamic_cast <numberMessageRange*>(m_ranges[0]) != NULL; +} + + +bool messageSet::isUIDSet() const +{ + return !isEmpty() && dynamic_cast <UIDMessageRange*>(m_ranges[0]) != NULL; +} + + +} // net +} // vmime diff --git a/src/net/pop3/POP3Folder.cpp b/src/net/pop3/POP3Folder.cpp index 9dc4589b..6a652de0 100644 --- a/src/net/pop3/POP3Folder.cpp +++ b/src/net/pop3/POP3Folder.cpp @@ -230,62 +230,36 @@ ref <message> POP3Folder::getMessage(const int num) } -std::vector <ref <message> > POP3Folder::getMessages(const int from, const int to) +std::vector <ref <message> > POP3Folder::getMessages(const messageSet& msgs) { ref <POP3Store> store = m_store.acquire(); - const int to2 = (to == -1 ? m_messageCount : to); - if (!store) throw exceptions::illegal_state("Store disconnected"); else if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount) - throw exceptions::message_not_found(); - - std::vector <ref <message> > v; - ref <POP3Folder> thisFolder = thisRef().dynamicCast <POP3Folder>(); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create <POP3Message>(thisFolder, i)); - - return (v); -} - - -ref <message> POP3Folder::getMessageByUID(const message::uid& /* uid */) -{ - throw exceptions::operation_not_supported(); -} - - -std::vector <ref <message> > POP3Folder::getMessagesByUID(const std::vector <message::uid>& /* uids */) -{ - throw exceptions::operation_not_supported(); -} + if (msgs.isNumberSet()) + { + const std::vector <int> numbers = POP3Utils::messageSetToNumberList(msgs); -std::vector <ref <message> > POP3Folder::getMessages(const std::vector <int>& nums) -{ - ref <POP3Store> store = m_store.acquire(); + std::vector <ref <message> > messages; + ref <POP3Folder> thisFolder = thisRef().dynamicCast <POP3Folder>(); - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); + for (std::vector <int>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + { + if (*it < 1|| *it > m_messageCount) + throw exceptions::message_not_found(); - std::vector <ref <message> > v; - ref <POP3Folder> thisFolder = thisRef().dynamicCast <POP3Folder>(); + messages.push_back(vmime::create <POP3Message>(thisFolder, *it)); + } - for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + return messages; + } + else { - if (*it < 1|| *it > m_messageCount) - throw exceptions::message_not_found(); - - v.push_back(vmime::create <POP3Message>(thisFolder, *it)); + throw exceptions::operation_not_supported(); } - - return (v); } @@ -560,99 +534,11 @@ void POP3Folder::onStoreDisconnected() } -void POP3Folder::deleteMessage(const int num) +void POP3Folder::deleteMessages(const messageSet& msgs) { ref <POP3Store> store = m_store.acquire(); - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - POP3Command::DELE(num)->send(store->getConnection()); - - ref <POP3Response> response = - POP3Response::readResponse(store->getConnection()); - - if (!response->isSuccess()) - throw exceptions::command_error("DELE", response->getFirstLine()); - - // Update local flags - for (std::map <POP3Message*, int>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - POP3Message* msg = (*it).first; - - if (msg->getNumber() == num) - msg->m_deleted = true; - } - - // Notify message flags changed - std::vector <int> nums; - nums.push_back(num); - - ref <events::messageChangedEvent> event = - vmime::create <events::messageChangedEvent> - (thisRef().dynamicCast <folder>(), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::deleteMessages(const int from, const int to) -{ - ref <POP3Store> store = m_store.acquire(); - - if (from < 1 || (to < from && to != -1)) - throw exceptions::invalid_argument(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - else if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - const int to2 = (to == -1 ? m_messageCount : to); - - for (int i = from ; i <= to2 ; ++i) - { - POP3Command::DELE(i)->send(store->getConnection()); - - ref <POP3Response> response = - POP3Response::readResponse(store->getConnection()); - - if (!response->isSuccess()) - throw exceptions::command_error("DELE", response->getFirstLine()); - } - - // Update local flags - for (std::map <POP3Message*, int>::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) - { - POP3Message* msg = (*it).first; - - if (msg->getNumber() >= from && msg->getNumber() <= to2) - msg->m_deleted = true; - } - - // Notify message flags changed - std::vector <int> nums; - - for (int i = from ; i <= to2 ; ++i) - nums.push_back(i); - - ref <events::messageChangedEvent> event = - vmime::create <events::messageChangedEvent> - (thisRef().dynamicCast <folder>(), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::deleteMessages(const std::vector <int>& nums) -{ - ref <POP3Store> store = m_store.acquire(); + const std::vector <int> nums = POP3Utils::messageSetToNumberList(msgs); if (nums.empty()) throw exceptions::invalid_argument(); @@ -702,14 +588,7 @@ void POP3Folder::deleteMessages(const std::vector <int>& nums) } -void POP3Folder::setMessageFlags(const int /* from */, const int /* to */, - const int /* flags */, const int /* mode */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::setMessageFlags(const std::vector <int>& /* nums */, +void POP3Folder::setMessageFlags(const messageSet& /* msgs */, const int /* flags */, const int /* mode */) { throw exceptions::operation_not_supported(); @@ -736,19 +615,7 @@ void POP3Folder::addMessage(utility::inputStream& /* is */, const int /* size */ } -void POP3Folder::copyMessage(const folder::path& /* dest */, const int /* num */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::copyMessages(const folder::path& /* dest */, const int /* from */, const int /* to */) -{ - throw exceptions::operation_not_supported(); -} - - -void POP3Folder::copyMessages(const folder::path& /* dest */, const std::vector <int>& /* nums */) +void POP3Folder::copyMessages(const folder::path& /* dest */, const messageSet& /* msgs */) { throw exceptions::operation_not_supported(); } diff --git a/src/net/pop3/POP3Utils.cpp b/src/net/pop3/POP3Utils.cpp index f239627f..e2722104 100644 --- a/src/net/pop3/POP3Utils.cpp +++ b/src/net/pop3/POP3Utils.cpp @@ -73,6 +73,38 @@ void POP3Utils::parseMultiListOrUidlResponse(ref <POP3Response> response, std::m } + +class POP3MessageSetEnumerator : public messageSetEnumerator +{ +public: + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + for (int i = range.getFirst(), last = range.getLast() ; i <= last ; ++i) + list.push_back(i); + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& /* range */) + { + // Not supported + } + +public: + + std::vector <int> list; +}; + + +// static +const std::vector <int> POP3Utils::messageSetToNumberList(const messageSet& msgs) +{ + POP3MessageSetEnumerator en; + msgs.enumerate(en); + + return en.list; +} + + } // pop3 } // net } // vmime diff --git a/tests/net/messageSetTest.cpp b/tests/net/messageSetTest.cpp new file mode 100644 index 00000000..3aef7ab0 --- /dev/null +++ b/tests/net/messageSetTest.cpp @@ -0,0 +1,200 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "tests/testUtils.hpp" + +#include "vmime/net/messageSet.hpp" + + +VMIME_TEST_SUITE_BEGIN(messageSetTest) + + VMIME_TEST_LIST_BEGIN + VMIME_TEST(testNumberSet_Single) + VMIME_TEST(testNumberSet_Range) + VMIME_TEST(testNumberSet_InfiniteRange) + VMIME_TEST(testNumberSet_Multiple) + VMIME_TEST(testUIDSet_Single) + VMIME_TEST(testUIDSet_Range) + VMIME_TEST(testUIDSet_InfiniteRange) + VMIME_TEST(testUIDSet_MultipleNumeric) + VMIME_TEST(testUIDSet_MultipleNonNumeric) + VMIME_TEST(testIsNumberSet) + VMIME_TEST(testIsUIDSet) + VMIME_TEST_LIST_END + + + class messageSetStringEnumerator : public vmime::net::messageSetEnumerator + { + public: + + messageSetStringEnumerator() + : m_first(true) + { + } + + void enumerateNumberMessageRange(const vmime::net::numberMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + void enumerateUIDMessageRange(const vmime::net::UIDMessageRange& range) + { + if (!m_first) + m_oss << ","; + + if (range.getFirst() == range.getLast()) + m_oss << range.getFirst(); + else + m_oss << range.getFirst() << ":" << range.getLast(); + + m_first = false; + } + + const std::string str() const + { + return m_oss.str(); + } + + private: + + std::ostringstream m_oss; + bool m_first; + }; + + + const std::string enumerateAsString(const vmime::net::messageSet& set) + { + messageSetStringEnumerator en; + set.enumerate(en); + + return en.str(); + } + + + void testNumberSet_Single() + { + VASSERT_EQ("str", "42", enumerateAsString(vmime::net::messageSet::byNumber(42))); + } + + void testNumberSet_Range() + { + VASSERT_EQ("str", "42:100", enumerateAsString(vmime::net::messageSet::byNumber(42, 100))); + } + + void testNumberSet_InfiniteRange() + { + VASSERT_EQ("str", "42:-1", enumerateAsString(vmime::net::messageSet::byNumber(42, -1))); + } + + void testNumberSet_Multiple() + { + std::vector <int> numbers; + numbers.push_back(1); // test grouping 1:3 + numbers.push_back(89); // test sorting + numbers.push_back(2); + numbers.push_back(3); + numbers.push_back(42); + numbers.push_back(53); // test grouping 53:57 + numbers.push_back(54); + numbers.push_back(55); + numbers.push_back(56); + numbers.push_back(56); // test duplicates + numbers.push_back(57); + numbers.push_back(99); + + VASSERT_EQ("str", "1:3,42,53:57,89,99", enumerateAsString(vmime::net::messageSet::byNumber(numbers))); + } + + + void testUIDSet_Single() + { + VASSERT_EQ("str", "abcdef", enumerateAsString(vmime::net::messageSet::byUID("abcdef"))); + } + + void testUIDSet_Range() + { + VASSERT_EQ("str", "abc:def", enumerateAsString(vmime::net::messageSet::byUID("abc:def"))); + } + + void testUIDSet_InfiniteRange() + { + VASSERT_EQ("str", "abc:*", enumerateAsString(vmime::net::messageSet::byUID("abc", "*"))); + } + + void testUIDSet_MultipleNumeric() + { + std::vector <vmime::net::message::uid> uids; + uids.push_back("1"); // test grouping 1:3 + uids.push_back("89"); // test sorting + uids.push_back("2"); + uids.push_back("3"); + uids.push_back("42"); + uids.push_back("53"); // test grouping 53:57 + uids.push_back("54"); + uids.push_back("55"); + uids.push_back("56"); + uids.push_back("56"); // test duplicates + uids.push_back("57"); + uids.push_back("99"); + + VASSERT_EQ("str", "1:3,42,53:57,89,99", enumerateAsString(vmime::net::messageSet::byUID(uids))); + } + + void testUIDSet_MultipleNonNumeric() + { + std::vector <vmime::net::message::uid> uids; + uids.push_back("12"); + uids.push_back("34"); + uids.push_back("ab56"); + uids.push_back("78cd"); + + VASSERT_EQ("str", "12,34,ab56,78cd", enumerateAsString(vmime::net::messageSet::byUID(uids))); + } + + void testIsNumberSet() + { + VASSERT_TRUE("number1", vmime::net::messageSet::byNumber(42).isNumberSet()); + VASSERT_FALSE("uid1", vmime::net::messageSet::byUID("42").isNumberSet()); + + VASSERT_TRUE("number2", vmime::net::messageSet::byNumber(42, -1).isNumberSet()); + VASSERT_FALSE("uid2", vmime::net::messageSet::byUID("42", "*").isNumberSet()); + } + + void testIsUIDSet() + { + VASSERT_FALSE("number1", vmime::net::messageSet::byNumber(42).isUIDSet()); + VASSERT_TRUE("uid1", vmime::net::messageSet::byUID("42").isUIDSet()); + + VASSERT_FALSE("number2", vmime::net::messageSet::byNumber(42, -1).isUIDSet()); + VASSERT_TRUE("uid2", vmime::net::messageSet::byUID("42", "*").isUIDSet()); + } + +VMIME_TEST_SUITE_END diff --git a/vmime/net/folder.hpp b/vmime/net/folder.hpp index 4cb6bb1a..fc878fbf 100644 --- a/vmime/net/folder.hpp +++ b/vmime/net/folder.hpp @@ -38,6 +38,7 @@ #include "vmime/message.hpp" #include "vmime/net/message.hpp" +#include "vmime/net/messageSet.hpp" #include "vmime/net/events.hpp" #include "vmime/net/folderStatus.hpp" @@ -186,38 +187,34 @@ public: */ virtual ref <message> getMessage(const int num) = 0; - /** Get new references to messages in this folder, given their numbers. + /** Get new references to messages in this folder, given either their + * sequence numbers or UIDs. * - * @param from sequence number of the first message to get - * @param to sequence number of the last message to get - * @return new objects referencing the specified messages - * @throw exceptions::net_exception if an error occurs - */ - virtual std::vector <ref <message> > getMessages(const int from = 1, const int to = -1) = 0; - - /** Get new references to messages in this folder, given their numbers. + * To retrieve messages by their number, use: + * \code{.cpp} + * // Get messages from sequence number 5 to sequence number 8 (including) + * folder->getMessage(vmime::net::messageSet::byNumber(5, 8)); * - * @param nums sequence numbers of the messages to retrieve - * @return new objects referencing the specified messages - * @throw exceptions::net_exception if an error occurs - */ - virtual std::vector <ref <message> > getMessages(const std::vector <int>& nums) = 0; - - /** Get message in this folder, given its UID. + * // Get all messages in the folder, starting from number 42 + * folder->getMessage(vmime::net::messageSet::byNumber(42, -1)); + * \endcode + * Or, to retrieve messages by their UID, use: + * \code{.cpp} + * // Get messages from UID 1000 to UID 1042 (including) + * folder->getMessage(vmime::net::messageSet::byUID(1000, 1042)); * - * @param uid UID of message to retrieve - * @return a new object referencing the specified message - * @throw exceptions::net_exception if an error occurs - */ - virtual ref <message> getMessageByUID(const message::uid& uid) = 0; - - /** Get messages in this folder, given their UIDs. + * // Get message with UID 1042 + * folder->getMessage(vmime::net::messageSet::byUID(1042)); + * + * // Get all messages in the folder, starting from UID 1000 + * folder->getMessage(vmime::net::messageSet::byUID(1000, "*")); + * \endcode * - * @param uids UIDs of messages to retrieve + * @param msgs index set of messages to retrieve * @return new objects referencing the specified messages * @throw exceptions::net_exception if an error occurs */ - virtual std::vector <ref <message> > getMessagesByUID(const std::vector <message::uid>& uids) = 0; + virtual std::vector <ref <message> > getMessages(const messageSet& msgs) = 0; /** Return the number of messages in this folder. * @@ -249,46 +246,21 @@ public: */ virtual void rename(const folder::path& newPath) = 0; - /** Remove a message in this folder. - * - * @param num sequence number of the message to delete - * @throw exceptions::net_exception if an error occurs - */ - virtual void deleteMessage(const int num) = 0; - - /** Remove one or more messages from this folder. - * - * @param from sequence number of the first message to delete - * @param to sequence number of the last message to delete - * @throw exceptions::net_exception if an error occurs - */ - virtual void deleteMessages(const int from = 1, const int to = -1) = 0; - /** Remove one or more messages from this folder. * - * @param nums sequence numbers of the messages to delete - * @throw exceptions::net_exception if an error occurs - */ - virtual void deleteMessages(const std::vector <int>& nums) = 0; - - /** Change the flags for one or more messages in this folder. - * - * @param from sequence number of the first message to modify - * @param to sequence number of the last message to modify - * @param flags set of flags (see message::Flags) - * @param mode indicate how to treat old and new flags (see message::FlagsModes) + * @param msgs index set of messages to delete * @throw exceptions::net_exception if an error occurs */ - virtual void setMessageFlags(const int from, const int to, const int flags, const int mode = message::FLAG_MODE_SET) = 0; + virtual void deleteMessages(const messageSet& msgs) = 0; /** Change the flags for one or more messages in this folder. * - * @param nums sequence numbers of the messages to modify + * @param msgs index set of messages on which to set the flags * @param flags set of flags (see message::Flags) * @param mode indicate how to treat old and new flags (see message::FlagsModes) * @throw exceptions::net_exception if an error occurs */ - virtual void setMessageFlags(const std::vector <int>& nums, const int flags, const int mode = message::FLAG_MODE_SET) = 0; + virtual void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET) = 0; /** Add a message to this folder. * @@ -311,30 +283,13 @@ public: */ virtual void addMessage(utility::inputStream& is, const int size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL) = 0; - /** Copy a message from this folder to another folder. - * - * @param dest destination folder path - * @param num sequence number of the message to copy - * @throw exceptions::net_exception if an error occurs - */ - virtual void copyMessage(const folder::path& dest, const int num) = 0; - - /** Copy messages from this folder to another folder. - * - * @param dest destination folder path - * @param from sequence number of the first message to copy - * @param to sequence number of the last message to copy - * @throw exceptions::net_exception if an error occurs - */ - virtual void copyMessages(const folder::path& dest, const int from = 1, const int to = -1) = 0; - /** Copy messages from this folder to another folder. * * @param dest destination folder path - * @param nums sequence numbers of the messages to copy + * @param msgs index set of messages to copy * @throw exceptions::net_exception if an error occurs */ - virtual void copyMessages(const folder::path& dest, const std::vector <int>& nums) = 0; + virtual void copyMessages(const folder::path& dest, const messageSet& msgs) = 0; /** Request folder status without opening it. * diff --git a/vmime/net/imap/IMAPFolder.hpp b/vmime/net/imap/IMAPFolder.hpp index c1d2d34a..b7fc46a1 100644 --- a/vmime/net/imap/IMAPFolder.hpp +++ b/vmime/net/imap/IMAPFolder.hpp @@ -91,11 +91,7 @@ public: bool isOpen() const; ref <message> getMessage(const int num); - std::vector <ref <message> > getMessages(const int from = 1, const int to = -1); - std::vector <ref <message> > getMessages(const std::vector <int>& nums); - - ref <message> getMessageByUID(const message::uid& uid); - std::vector <ref <message> > getMessagesByUID(const std::vector <message::uid>& uids); + std::vector <ref <message> > getMessages(const messageSet& msgs); std::vector <int> getMessageNumbersStartingOnUID(const message::uid& uid); @@ -106,19 +102,14 @@ public: void rename(const folder::path& newPath); - void deleteMessage(const int num); - void deleteMessages(const int from = 1, const int to = -1); - void deleteMessages(const std::vector <int>& nums); + void deleteMessages(const messageSet& msgs); - void setMessageFlags(const int from, const int to, const int flags, const int mode = message::FLAG_MODE_SET); - void setMessageFlags(const std::vector <int>& nums, const int flags, const int mode = message::FLAG_MODE_SET); + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); void addMessage(ref <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); void addMessage(utility::inputStream& is, const int size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); - void copyMessage(const folder::path& dest, const int num); - void copyMessages(const folder::path& dest, const int from = 1, const int to = -1); - void copyMessages(const folder::path& dest, const std::vector <int>& nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref <folderStatus> getStatus(); diff --git a/vmime/net/imap/IMAPUtils.hpp b/vmime/net/imap/IMAPUtils.hpp index 7a03fcac..1dfde7a9 100644 --- a/vmime/net/imap/IMAPUtils.hpp +++ b/vmime/net/imap/IMAPUtils.hpp @@ -73,29 +73,6 @@ public: static const string messageFlagList(const int flags); - /** Build an "IMAP set" given a list of message numbers. The function tries - * to group consecutive message numbers to reduce the list. - * - * Example: - * IN = "1,2,3,4,5,7,8,13,15,16,17" - * OUT = "1:5,7:8,13,15:*" for a mailbox with a total of 17 messages (max = 17) - * - * @param list list of message numbers - * @param max number of messages in the mailbox (or -1 if not known) - * @param alreadySorted set to true if the list of message numbers is - * already sorted in ascending order - * @return a set corresponding to the message list - */ - static const string listToSet(const std::vector <int>& list, - const int max = -1, const bool alreadySorted = false); - - /** Build an "IMAP set" set given a list of message UIDs. - * - * @param list list of message UIDs - * @return a set corresponding to the list - */ - static const string listToSet(const std::vector <message::uid>& list); - /** Format a date/time to IMAP date/time format. * * @param date date/time to format @@ -103,25 +80,16 @@ public: */ static const string dateTime(const vmime::datetime& date); - /** Construct a fetch request for the specified messages, designated by their sequence numbers. - * - * @param cnt connection - * @param list list of message numbers - * @param options fetch options - * @return fetch request - */ - static const string buildFetchRequest - (ref <IMAPConnection> cnt, const std::vector <int>& list, const int options); - - /** Construct a fetch request for the specified messages, designated by their UIDs. + /** Construct a fetch request for the specified messages, designated + * either by their sequence numbers or their UIDs. * * @param cnt connection - * @param list list of message UIDs + * @param msgs message set * @param options fetch options * @return fetch request */ static const string buildFetchRequest - (ref <IMAPConnection> cnt, const std::vector <message::uid>& list, const int options); + (ref <IMAPConnection> cnt, const messageSet& msgs, const int options); /** Convert a parser-style address list to a mailbox list. * @@ -130,6 +98,20 @@ public: */ static void convertAddressList(const IMAPParser::address_list& src, mailboxList& dest); + /** Returns an IMAP-formatted sequence set given a message set. + * + * @param msgs message set + * @return IMAP sequence set (eg. "1:5,7,15:*") + */ + static const string messageSetToSequenceSet(const messageSet& msgs); + + /** Returns a list of message sequence numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); + private: static const string buildFetchRequestImpl diff --git a/vmime/net/maildir/maildirFolder.hpp b/vmime/net/maildir/maildirFolder.hpp index 940fcaae..2b6f8b4d 100644 --- a/vmime/net/maildir/maildirFolder.hpp +++ b/vmime/net/maildir/maildirFolder.hpp @@ -89,11 +89,7 @@ public: bool isOpen() const; ref <message> getMessage(const int num); - std::vector <ref <message> > getMessages(const int from = 1, const int to = -1); - std::vector <ref <message> > getMessages(const std::vector <int>& nums); - - ref <message> getMessageByUID(const message::uid& uid); - std::vector <ref <message> > getMessagesByUID(const std::vector <message::uid>& uids); + std::vector <ref <message> > getMessages(const messageSet& msgs); int getMessageCount(); @@ -102,19 +98,14 @@ public: void rename(const folder::path& newPath); - void deleteMessage(const int num); - void deleteMessages(const int from = 1, const int to = -1); - void deleteMessages(const std::vector <int>& nums); + void deleteMessages(const messageSet& msgs); - void setMessageFlags(const int from, const int to, const int flags, const int mode = message::FLAG_MODE_SET); - void setMessageFlags(const std::vector <int>& nums, const int flags, const int mode = message::FLAG_MODE_SET); + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); void addMessage(ref <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); void addMessage(utility::inputStream& is, const int size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); - void copyMessage(const folder::path& dest, const int num); - void copyMessages(const folder::path& dest, const int from = 1, const int to = -1); - void copyMessages(const folder::path& dest, const std::vector <int>& nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref <folderStatus> getStatus(); diff --git a/vmime/net/maildir/maildirUtils.hpp b/vmime/net/maildir/maildirUtils.hpp index 22246fb7..072a2648 100644 --- a/vmime/net/maildir/maildirUtils.hpp +++ b/vmime/net/maildir/maildirUtils.hpp @@ -34,6 +34,8 @@ #include "vmime/utility/file.hpp" #include "vmime/utility/path.hpp" +#include "vmime/net/messageSet.hpp" + namespace vmime { namespace net { @@ -129,6 +131,13 @@ public: * @param dir directory to delete */ static void recursiveFSDelete(ref <utility::file> dir); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); }; diff --git a/vmime/net/messageSet.hpp b/vmime/net/messageSet.hpp new file mode 100644 index 00000000..2535ac25 --- /dev/null +++ b/vmime/net/messageSet.hpp @@ -0,0 +1,335 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#ifndef VMIME_NET_MESSAGESET_HPP_INCLUDED +#define VMIME_NET_MESSAGESET_HPP_INCLUDED + + +#include "vmime/net/message.hpp" + + +namespace vmime { +namespace net { + + +// Forward references +class numberMessageRange; +class UIDMessageRange; + + +/** Enumerator used to retrieve the message number/UID ranges contained + * in a messageSet object. + */ + +class VMIME_EXPORT messageSetEnumerator +{ +public: + + virtual void enumerateNumberMessageRange(const numberMessageRange& range) = 0; + virtual void enumerateUIDMessageRange(const UIDMessageRange& range) = 0; +}; + + +/** A range of (continuous) messages, designated either by their + * sequence number, or by their UID. + */ + +class messageRange : public object +{ +public: + + virtual ~messageRange(); + + /** Enumerates this range with the specified enumerator. + * + * @param en enumerator that will receive the method calls while + * enumerating this range + */ + virtual void enumerate(messageSetEnumerator& en) const = 0; + + /** Clones this message range. + */ + virtual messageRange* clone() const = 0; + +protected: + + messageRange(); + messageRange(const messageRange&); +}; + + +/** A range of (continuous) messages designated by their sequence number. + */ + +class numberMessageRange : public messageRange +{ +public: + + /** Constructs a message range containing a single message. + * + * @param number message number (numbering starts at 1, not 0) + */ + numberMessageRange(const int number); + + /** Constructs a message range for multiple messages. + * + * @param first number of the first message in the range (numbering + * starts at 1, not 0) + * @param last number of the last message in the range, or use the + * special value -1 to designate the last message in the folder + */ + numberMessageRange(const int first, const int last); + + /** Constructs a message range by copying from another range. + * + * @param other range to copy + */ + numberMessageRange(const numberMessageRange& other); + + /** Returns the number of the first message in the range. + * + * @return number of the first message + */ + int getFirst() const; + + /** Returns the number of the last message in the range, or -1 + * to designate the last message in the folder + * + * @return number of the last message + */ + int getLast() const; + + void enumerate(messageSetEnumerator& en) const; + + messageRange* clone() const; + +private: + + int m_first, m_last; +}; + + +/** A range of (continuous) messages represented by their UID. + */ + +class UIDMessageRange : public messageRange +{ +public: + + /** Constructs a message range containing a single message. + * + * @param uid message UID + */ + UIDMessageRange(const message::uid& uid); + + /** Constructs a message range for multiple messages. + * + * @param first UID of the first message in the range + * @param last UID of the last message in the range, or use the + * special value '*' to designate the last message in the folder + */ + UIDMessageRange(const message::uid& first, const message::uid& last); + + /** Constructs a message range by copying from another range. + * + * @param other range to copy + */ + UIDMessageRange(const UIDMessageRange& other); + + /** Returns the UID of the first message in the range. + * + * @return UID of the first message + */ + const message::uid getFirst() const; + + /** Returns the UID of the last message in the range, or '*' + * to designate the last message in the folder + * + * @return UID of the last message + */ + const message::uid getLast() const; + + void enumerate(messageSetEnumerator& en) const; + + messageRange* clone() const; + +private: + + message::uid m_first, m_last; +}; + + +/** Represents a set of messages, designated either by their sequence + * number, or by their UID (but not both). + * + * Following is example code to designate messages by their number: + * \code{.cpp} + * // Designate a single message with sequence number 42 + * vmime::net::messageSet::byNumber(42) + * + * // Designate messages from sequence number 5 to sequence number 8 (including) + * vmime::net::messageSet::byNumber(5, 8) + * + * // Designate all messages in the folder, starting from number 42 + * vmime::net::messageSet::byNumber(42, -1) + * \endcode + * Or, to designate messages by their UID, use: + * \code{.cpp} + * // Designate a single message with UID 1042 + * vmime::net::messageSet::byUID(1042) + * + * // Designate messages from UID 1000 to UID 1042 (including) + * vmime::net::messageSet::byUID(1000, 1042) + * + * // Designate all messages in the folder, starting from UID 1000 + * vmime::net::messageSet::byUID(1000, "*") + * \endcode + */ + +class VMIME_EXPORT messageSet : public object +{ +public: + + ~messageSet(); + + messageSet(const messageSet& other); + + /** Constructs a new message set and initializes it with a single + * message represented by its sequence number. + * + * @param number message number (numbering starts at 1, not 0) + * @return new message set + */ + static messageSet byNumber(const int number); + + /** Constructs a new message set and initializes it with a range + * of messages represented by their sequence number. + * + * @param first number of the first message in the range (numbering + * starts at 1, not 0) + * @param last number of the last message in the range, or use the + * special value -1 to designate the last message in the folder + * @return new message set + */ + static messageSet byNumber(const int first, const int last); + + /** Constructs a new message set and initializes it with a possibly + * unsorted list of messages represented by their sequence number. + * Please note that numbering starts at 1, not 0. + * + * The function tries to group consecutive message numbers into + * ranges to reduce the size of the resulting set. + * + * For example, given the list "1,2,3,4,5,7,8,13,15,16,17" it will + * result in the following ranges: "1:5,7:8,13,15:17". + * + * @param numbers a vector containing numbers of the messages + * @return new message set + */ + static messageSet byNumber(const std::vector <int>& numbers); + + /** Constructs a new message set and initializes it with a single + * message represented by its UID. + * + * @param uid message UID + * @return new message set + */ + static messageSet byUID(const message::uid& uid); + + /** Constructs a new message set and initializes it with a range + * of messages represented by their sequence number. + * + * @param first UID of the first message in the range + * @param last UID of the last message in the range, or use the + * special value '*' to designate the last message in the folder + * @return new message set + */ + static messageSet byUID(const message::uid& first, const message::uid& last); + + /** Constructs a new message set and initializes it with a possibly + * unsorted list of messages represented by their UID. + * + * For UIDs that actually are numbers (this is the case for IMAP), the + * function tries to group consecutive UIDs into ranges to reduce the + * size of the resulting set. + * + * For example, given the list "1,2,3,4,5,7,8,13,15,16,17" it will + * result in the following ranges: "1:5,7:8,13,15:17". + * + * @param uids a vector containing UIDs of the messages + * @return new message set + */ + static messageSet byUID(const std::vector <message::uid>& uids); + + /** Adds the specified range to this set. The type of message range + * (either number or UID) must match the type of the ranges already + * contained in this set (ie. it's not possible to have a message + * set which contains both number ranges and UID ranges). + * + * @param range range to add + * @throw std::invalid_argument exception if the range type does + * not match the type of the ranges in this set + */ + void addRange(const messageRange& range); + + /** Enumerates this set with the specified enumerator. + * + * @param en enumerator that will receive the method calls while + * enumerating the ranges in this set + */ + void enumerate(messageSetEnumerator& en) const; + + /** Returns whether this set is empty (contains no range). + * + * @return true if this set is empty, or false otherwise + */ + bool isEmpty() const; + + /** Returns whether this set references messages by their sequence + * number. + * + * @return true if this set references messages by their sequence + * number, or false otherwise + */ + bool isNumberSet() const; + + /** Returns whether this set references messages by their UID. + * + * @return true if this set references messages by their UID, + * or false otherwise + */ + bool isUIDSet() const; + +private: + + messageSet(); + + std::vector <messageRange*> m_ranges; +}; + + +} // net +} // vmime + + +#endif // VMIME_NET_MESSAGESET_HPP_INCLUDED diff --git a/vmime/net/pop3/POP3Folder.hpp b/vmime/net/pop3/POP3Folder.hpp index 8a97213c..93c1d257 100644 --- a/vmime/net/pop3/POP3Folder.hpp +++ b/vmime/net/pop3/POP3Folder.hpp @@ -86,11 +86,7 @@ public: bool isOpen() const; ref <message> getMessage(const int num); - std::vector <ref <message> > getMessages(const int from = 1, const int to = -1); - std::vector <ref <message> > getMessages(const std::vector <int>& nums); - - ref <message> getMessageByUID(const message::uid& uid); - std::vector <ref <message> > getMessagesByUID(const std::vector <message::uid>& uids); + std::vector <ref <message> > getMessages(const messageSet& msgs); int getMessageCount(); @@ -99,19 +95,14 @@ public: void rename(const folder::path& newPath); - void deleteMessage(const int num); - void deleteMessages(const int from = 1, const int to = -1); - void deleteMessages(const std::vector <int>& nums); + void deleteMessages(const messageSet& msgs); - void setMessageFlags(const int from, const int to, const int flags, const int mode = message::FLAG_MODE_SET); - void setMessageFlags(const std::vector <int>& nums, const int flags, const int mode = message::FLAG_MODE_SET); + void setMessageFlags(const messageSet& msgs, const int flags, const int mode = message::FLAG_MODE_SET); void addMessage(ref <vmime::message> msg, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); void addMessage(utility::inputStream& is, const int size, const int flags = message::FLAG_UNDEFINED, vmime::datetime* date = NULL, utility::progressListener* progress = NULL); - void copyMessage(const folder::path& dest, const int num); - void copyMessages(const folder::path& dest, const int from = 1, const int to = -1); - void copyMessages(const folder::path& dest, const std::vector <int>& nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref <folderStatus> getStatus(); diff --git a/vmime/net/pop3/POP3Utils.hpp b/vmime/net/pop3/POP3Utils.hpp index 7a2376a1..9d20431c 100644 --- a/vmime/net/pop3/POP3Utils.hpp +++ b/vmime/net/pop3/POP3Utils.hpp @@ -35,6 +35,8 @@ #include "vmime/types.hpp" +#include "vmime/net/messageSet.hpp" + namespace vmime { namespace net { @@ -63,6 +65,13 @@ public: */ static void parseMultiListOrUidlResponse (ref <POP3Response> response, std::map <int, string>& result); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector <int> messageSetToNumberList(const messageSet& msgs); }; |