From b0845eff0db7d3aa35c3dc1629d250535d704bc8 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Fri, 26 Jul 2013 14:47:30 +0200 Subject: [PATCH] Allow messages to be designated either by their number or their UID. Warning: this is an API-breaking change. --- SConstruct | 2 + doc/book/net.tex | 14 +- src/net/imap/IMAPFolder.cpp | 377 ++++++------------------- src/net/imap/IMAPMessage.cpp | 5 +- src/net/imap/IMAPUtils.cpp | 213 +++++++------- src/net/maildir/maildirFolder.cpp | 414 ++++++++-------------------- src/net/maildir/maildirMessage.cpp | 2 +- src/net/maildir/maildirUtils.cpp | 32 +++ src/net/messageSet.cpp | 369 +++++++++++++++++++++++++ src/net/pop3/POP3Folder.cpp | 179 ++---------- src/net/pop3/POP3Utils.cpp | 32 +++ tests/net/messageSetTest.cpp | 200 ++++++++++++++ vmime/net/folder.hpp | 107 +++---- vmime/net/imap/IMAPFolder.hpp | 17 +- vmime/net/imap/IMAPUtils.hpp | 54 ++-- vmime/net/maildir/maildirFolder.hpp | 17 +- vmime/net/maildir/maildirUtils.hpp | 9 + vmime/net/messageSet.hpp | 335 ++++++++++++++++++++++ vmime/net/pop3/POP3Folder.hpp | 17 +- vmime/net/pop3/POP3Utils.hpp | 9 + 20 files changed, 1391 insertions(+), 1013 deletions(-) create mode 100644 src/net/messageSet.cpp create mode 100644 tests/net/messageSetTest.cpp create mode 100644 vmime/net/messageSet.hpp diff --git a/SConstruct b/SConstruct index ed0a4d03..baff95c0 100644 --- a/SConstruct +++ b/SConstruct @@ -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 > allMessages = folder->getMessages(); +std::vector > 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 folder = store->getDefaultFolder(); -folder->deleteMessage(3); -folder->deleteMessage(2); +folder->deleteMessages(vmime::net::messageSet::byNumber(/* from */ 2, /* to */ 3)); // This is equivalent std::vector 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 IMAPFolder::getMessage(const int num) } -std::vector > IMAPFolder::getMessages(const int from, const int to) -{ - 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 > v; - ref thisFolder = thisRef().dynamicCast (); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create (thisFolder, i)); - - return (v); -} - - -std::vector > IMAPFolder::getMessages(const std::vector & nums) +std::vector > IMAPFolder::getMessages(const messageSet& msgs) { if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - std::vector > v; - ref thisFolder = thisRef().dynamicCast (); - - for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - v.push_back(vmime::create (thisFolder, *it)); - - return (v); -} - - -ref IMAPFolder::getMessageByUID(const message::uid& uid) -{ - std::vector uids; - uids.push_back(uid); - - std::vector > msgs = getMessagesByUID(uids); - - if (msgs.size() == 0) - throw exceptions::message_not_found(); - - return msgs[0]; -} - - -std::vector > IMAPFolder::getMessagesByUID(const std::vector & uids) -{ - if (!isOpen()) - throw exceptions::illegal_state("Folder not open"); - - if (uids.size() == 0) + if (msgs.isEmpty() == 0) return std::vector >(); - // 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 - - // Prepare command and arguments - std::ostringstream cmd; - cmd.imbue(std::locale::classic()); - - cmd << "UID FETCH " << uids[0]; - - for (std::vector ::size_type i = 1, n = uids.size() ; i < n ; ++i) - cmd << "," << uids[i]; - - cmd << " UID"; - - // Send the request - m_connection->send(true, cmd.str(), true); - - // Get the response - utility::auto_ptr 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("UID FETCH ... UID", resp->getErrorLog(), "bad response"); - } - - // Process the response - const std::vector & respDataList = - resp->continue_req_or_response_data(); - std::vector > messages; - for (std::vector ::const_iterator - it = respDataList.begin() ; it != respDataList.end() ; ++it) + if (msgs.isNumberSet()) { - if ((*it)->response_data() == NULL) + const std::vector numbers = IMAPUtils::messageSetToNumberList(msgs); + + ref thisFolder = thisRef().dynamicCast (); + + for (std::vector ::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) + messages.push_back(vmime::create (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 + + // Prepare command and arguments + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + + cmd << "UID FETCH " << IMAPUtils::messageSetToSequenceSet(msgs) << " UID"; + + // Send the request + m_connection->send(true, cmd.str(), true); + + // Get the response + utility::auto_ptr 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("UID FETCH ... UID", - resp->getErrorLog(), "invalid response"); + throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "bad response"); } - const IMAPParser::message_data* messageData = - (*it)->response_data()->message_data(); + // Process the response + const std::vector & respDataList = + resp->continue_req_or_response_data(); - // 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 (messageData->number()); - message::uid msgUID; - - // Find UID in message attributes - const std::vector atts = messageData->msg_att()->items(); - - for (std::vector ::const_iterator - it = atts.begin() ; it != atts.end() ; ++it) + for (std::vector ::const_iterator + it = respDataList.begin() ; it != respDataList.end() ; ++it) { - if ((*it)->type() == IMAPParser::msg_att_item::UID) + if ((*it)->response_data() == NULL) { - msgUID = (*it)->unique_id()->value(); - break; + throw exceptions::command_error("UID FETCH ... UID", + resp->getErrorLog(), "invalid response"); } - } - if (!msgUID.empty()) - { - ref thisFolder = thisRef().dynamicCast (); - messages.push_back(vmime::create (thisFolder, msgNum, msgUID)); + 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; + + // Get Process fetch response for this message + const int msgNum = static_cast (messageData->number()); + message::uid msgUID; + + // Find UID in message attributes + const std::vector atts = messageData->msg_att()->items(); + + for (std::vector ::const_iterator + it = atts.begin() ; it != atts.end() ; ++it) + { + if ((*it)->type() == IMAPParser::msg_att_item::UID) + { + msgUID = (*it)->unique_id()->value(); + break; + } + } + + if (!msgUID.empty()) + { + ref thisFolder = thisRef().dynamicCast (); + messages.push_back(vmime::create (thisFolder, msgNum, msgUID)); + } } } @@ -816,7 +774,9 @@ void IMAPFolder::fetchMessages(std::vector >& 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 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 & nums) +void IMAPFolder::setMessageFlags(const messageSet& msgs, const int flags, const int mode) { - ref 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 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 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 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 & nums, const int flags, const int mode) -{ - ref 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 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,7 +1209,7 @@ void IMAPFolder::rename(const folder::path& newPath) } -void IMAPFolder::copyMessage(const folder::path& dest, const int num) +void IMAPFolder::copyMessages(const folder::path& dest, const messageSet& set) { ref store = m_store.acquire(); @@ -1373,63 +1218,11 @@ void IMAPFolder::copyMessage(const folder::path& dest, const int num) 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) -{ - ref 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 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 & nums) -{ - ref 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 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 @@ -471,98 +471,6 @@ const string IMAPUtils::messageFlagList(const int flags) } -// static -const string IMAPUtils::listToSet(const std::vector & list, const int max, - const bool alreadySorted) -{ - // Sort a copy of the list (if not already sorted) - std::vector temp; - - if (!alreadySorted) - { - temp.resize(list.size()); - std::copy(list.begin(), list.end(), temp.begin()); - - std::sort(temp.begin(), temp.end()); - } - - const std::vector & theList = (alreadySorted ? list : temp); - - // Build the set - std::ostringstream res; - res.imbue(std::locale::classic()); - - int previous = -1, setBegin = -1; - - for (std::vector ::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 & list) -{ - if (list.size() == 0) - return ""; - - std::ostringstream res; - res.imbue(std::locale::classic()); - - res << list[0]; - - for (std::vector ::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) { @@ -633,8 +541,8 @@ const string IMAPUtils::dateTime(const vmime::datetime& date) // static -const string IMAPUtils::buildFetchRequestImpl - (ref cnt, const string& mode, const string& set, const int options) +const string IMAPUtils::buildFetchRequest + (ref 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 ::const_iterator it = items.begin() ; it != items.end() ; ++it) @@ -720,22 +628,6 @@ const string IMAPUtils::buildFetchRequestImpl } -// static -const string IMAPUtils::buildFetchRequest - (ref cnt, const std::vector & list, const int options) -{ - return buildFetchRequestImpl(cnt, "number", listToSet(list, -1, false), options); -} - - -// static -const string IMAPUtils::buildFetchRequest - (ref cnt, const std::vector & 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 & list() const + { + return m_list; + } + +public: + + std::vector m_list; +}; + + + +// static +const string IMAPUtils::messageSetToSequenceSet(const messageSet& msgs) +{ + IMAPUIDMessageSetEnumerator en; + msgs.enumerate(en); + + return en.str(); +} + + +// static +const std::vector 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 maildirFolder::getMessage(const int num) } -std::vector > 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 > v; - ref thisFolder = thisRef().dynamicCast (); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create (thisFolder, i)); - - return (v); -} - - -std::vector > maildirFolder::getMessages(const std::vector & nums) +std::vector > maildirFolder::getMessages(const messageSet& msgs) { if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - std::vector > v; - ref thisFolder = thisRef().dynamicCast (); + if (msgs.isNumberSet()) + { + const std::vector numbers = maildirUtils::messageSetToNumberList(msgs); - for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) - v.push_back(vmime::create (thisFolder, *it)); + std::vector > messages; + ref thisFolder = thisRef().dynamicCast (); - return (v); -} + for (std::vector ::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 (thisFolder, *it)); + } -ref maildirFolder::getMessageByUID(const message::uid& /* uid */) -{ - throw exceptions::operation_not_supported(); -} - - -std::vector > maildirFolder::getMessagesByUID(const std::vector & /* uids */) -{ - throw exceptions::operation_not_supported(); + return messages; + } + else + { + throw exceptions::operation_not_supported(); + } } @@ -590,118 +573,15 @@ void maildirFolder::rename(const folder::path& newPath) } -void maildirFolder::deleteMessage(const int num) +void maildirFolder::deleteMessages(const messageSet& msgs) { // 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 & nums) -{ - // 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) -{ - ref 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"); - - // Construct the list of message numbers - const int to2 = (to == -1) ? m_messageCount : to; - const int count = to - from + 1; - - std::vector nums; - nums.resize(count); - - for (int i = from, j = 0 ; i <= to2 ; ++i, ++j) - nums[j] = i; - - // Change message flags - setMessageFlagsImpl(nums, flags, mode); - - // Update local flags - switch (mode) - { - case message::FLAG_MODE_ADD: - { - 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 |= flags; - } - } - - break; - } - case message::FLAG_MODE_REMOVE: - { - 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 &= ~flags; - } - } - - break; - } - default: - case message::FLAG_MODE_SET: - { - 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 = flags; - } - } - - break; - } - - } - - // Notify message flags changed - ref event = - vmime::create - (thisRef().dynamicCast (), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); - - // TODO: notify other folders with the same path -} - - -void maildirFolder::setMessageFlags - (const std::vector & nums, const int flags, const int mode) + (const messageSet& msgs, const int flags, const int mode) { ref store = m_store.acquire(); @@ -712,124 +592,116 @@ void maildirFolder::setMessageFlags 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()); - - // Change message flags - setMessageFlagsImpl(list, flags, mode); - - // Update local flags - switch (mode) + if (msgs.isNumberSet()) { - case message::FLAG_MODE_ADD: - { - for (std::vector ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + const std::vector nums = maildirUtils::messageSetToNumberList(msgs); + + // Change message flags + ref fsf = platform::getHandler()->getFileSystemFactory(); + + utility::file::path curDirPath = store->getFormat()-> + folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); + + for (std::vector ::const_iterator it = + nums.begin() ; it != nums.end() ; ++it) { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) + const int num = *it - 1; + + try { - (*it)->m_flags |= flags; + const utility::file::path::component path = m_messageInfos[num].path; + ref file = fsf->create(curDirPath / path); + + int newFlags = maildirUtils::extractFlags(path); + + 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; + } + + const utility::file::path::component newPath = maildirUtils::buildFilename + (maildirUtils::extractId(path), newFlags); + + file->rename(curDirPath / newPath); + + if (flags & message::FLAG_DELETED) + m_messageInfos[num].type = messageInfos::TYPE_DELETED; + else + m_messageInfos[num].type = messageInfos::TYPE_CUR; + + m_messageInfos[num].path = newPath; + } + catch (exceptions::filesystem_exception& e) + { + // Ignore (not important) } } - break; - } - case message::FLAG_MODE_REMOVE: - { - for (std::vector ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + // Update local flags + switch (mode) { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) - { - (*it)->m_flags &= ~flags; - } - } - - break; - } - default: - case message::FLAG_MODE_SET: - { - for (std::vector ::iterator it = - m_messages.begin() ; it != m_messages.end() ; ++it) + case message::FLAG_MODE_ADD: { - if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) && - (*it)->m_flags != message::FLAG_UNDEFINED) + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) { - (*it)->m_flags = flags; - } - } - - break; - } - - } - - // Notify message flags changed - ref event = - vmime::create - (thisRef().dynamicCast (), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); - - // TODO: notify other folders with the same path -} - - -void maildirFolder::setMessageFlagsImpl - (const std::vector & nums, const int flags, const int mode) -{ - ref store = m_store.acquire(); - - ref fsf = platform::getHandler()->getFileSystemFactory(); - - utility::file::path curDirPath = store->getFormat()-> - folderPathToFileSystemPath(m_path, maildirFormat::CUR_DIRECTORY); - - for (std::vector ::const_iterator it = - nums.begin() ; it != nums.end() ; ++it) - { - const int num = *it - 1; - - try - { - const utility::file::path::component path = m_messageInfos[num].path; - ref file = fsf->create(curDirPath / path); - - int newFlags = maildirUtils::extractFlags(path); - - 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; + 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); - - file->rename(curDirPath / newPath); - - if (flags & message::FLAG_DELETED) - m_messageInfos[num].type = messageInfos::TYPE_DELETED; - else - m_messageInfos[num].type = messageInfos::TYPE_CUR; - - m_messageInfos[num].path = newPath; + break; } - catch (exceptions::filesystem_exception& e) + case message::FLAG_MODE_REMOVE: { - // Ignore (not important) + for (std::vector ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + 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 ::iterator it = + m_messages.begin() ; it != m_messages.end() ; ++it) + { + 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 event = + vmime::create + (thisRef().dynamicCast (), + events::messageChangedEvent::TYPE_FLAGS, nums); + + notifyMessageChanged(event); + + // TODO: notify other folders with the same path + } + else + { + throw exceptions::operation_not_supported(); } } @@ -1032,7 +904,7 @@ void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath, } -void maildirFolder::copyMessage(const folder::path& dest, const int num) +void maildirFolder::copyMessages(const folder::path& dest, const messageSet& msgs) { ref store = m_store.acquire(); @@ -1041,54 +913,6 @@ void maildirFolder::copyMessage(const folder::path& dest, const int num) 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 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 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 & nums) -{ - ref store = m_store.acquire(); - - if (!store) - throw exceptions::illegal_state("Store disconnected"); - 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 & nums) -{ - ref store = m_store.acquire(); - ref 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 nums = maildirUtils::messageSetToNumberList(msgs); + try { for (std::vector ::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 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 list; +}; + + +// static +const std::vector 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 +// +// 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 +#include +#include + + +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 & numbers) +{ + // Sort a copy of the list + std::vector 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 ::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 & uids) +{ + std::vector 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 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 ::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() << rangeStart))->str(), + static_cast (&(std::ostringstream() << previous))->str())); + + previous = current; + rangeStart = current; + } + } + } + + set.m_ranges.push_back(new UIDMessageRange + (static_cast (&(std::ostringstream() << rangeStart))->str(), + static_cast (&(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 (m_ranges[0]) != NULL; +} + + +bool messageSet::isUIDSet() const +{ + return !isEmpty() && dynamic_cast (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,42 +230,7 @@ ref POP3Folder::getMessage(const int num) } -std::vector > POP3Folder::getMessages(const int from, const int to) -{ - ref 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 > v; - ref thisFolder = thisRef().dynamicCast (); - - for (int i = from ; i <= to2 ; ++i) - v.push_back(vmime::create (thisFolder, i)); - - return (v); -} - - -ref POP3Folder::getMessageByUID(const message::uid& /* uid */) -{ - throw exceptions::operation_not_supported(); -} - - -std::vector > POP3Folder::getMessagesByUID(const std::vector & /* uids */) -{ - throw exceptions::operation_not_supported(); -} - - -std::vector > POP3Folder::getMessages(const std::vector & nums) +std::vector > POP3Folder::getMessages(const messageSet& msgs) { ref store = m_store.acquire(); @@ -274,18 +239,27 @@ std::vector > POP3Folder::getMessages(const std::vector & nu else if (!isOpen()) throw exceptions::illegal_state("Folder not open"); - std::vector > v; - ref thisFolder = thisRef().dynamicCast (); - - for (std::vector ::const_iterator it = nums.begin() ; it != nums.end() ; ++it) + if (msgs.isNumberSet()) { - if (*it < 1|| *it > m_messageCount) - throw exceptions::message_not_found(); + const std::vector numbers = POP3Utils::messageSetToNumberList(msgs); - v.push_back(vmime::create (thisFolder, *it)); + std::vector > messages; + ref thisFolder = thisRef().dynamicCast (); + + for (std::vector ::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 (thisFolder, *it)); + } + + return messages; + } + else + { + 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 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 response = - POP3Response::readResponse(store->getConnection()); - - if (!response->isSuccess()) - throw exceptions::command_error("DELE", response->getFirstLine()); - - // Update local flags - for (std::map ::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 nums; - nums.push_back(num); - - ref event = - vmime::create - (thisRef().dynamicCast (), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::deleteMessages(const int from, const int to) -{ - ref 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 response = - POP3Response::readResponse(store->getConnection()); - - if (!response->isSuccess()) - throw exceptions::command_error("DELE", response->getFirstLine()); - } - - // Update local flags - for (std::map ::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 nums; - - for (int i = from ; i <= to2 ; ++i) - nums.push_back(i); - - ref event = - vmime::create - (thisRef().dynamicCast (), - events::messageChangedEvent::TYPE_FLAGS, nums); - - notifyMessageChanged(event); -} - - -void POP3Folder::deleteMessages(const std::vector & nums) -{ - ref store = m_store.acquire(); + const std::vector nums = POP3Utils::messageSetToNumberList(msgs); if (nums.empty()) throw exceptions::invalid_argument(); @@ -702,14 +588,7 @@ void POP3Folder::deleteMessages(const std::vector & 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 & /* 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 & /* 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 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 list; +}; + + +// static +const std::vector 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 +// +// 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 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 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 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 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 + * 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)); + * + * // 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)); + * + * // 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 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 > getMessages(const int from = 1, const int to = -1) = 0; - - /** Get new references to messages in this folder, given their numbers. - * - * @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 > getMessages(const std::vector & nums) = 0; - - /** Get message in this folder, given its UID. - * - * @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 getMessageByUID(const message::uid& uid) = 0; - - /** Get messages in this folder, given their UIDs. - * - * @param uids UIDs of messages to retrieve - * @return new objects referencing the specified messages - * @throw exceptions::net_exception if an error occurs - */ - virtual std::vector > getMessagesByUID(const std::vector & uids) = 0; + virtual std::vector > 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 + * @param msgs index set of messages 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 & nums) = 0; + virtual void deleteMessages(const messageSet& msgs) = 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 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 int from, const int to, const int flags, const int mode = message::FLAG_MODE_SET) = 0; - - /** Change the flags for one or more messages in this folder. - * - * @param nums sequence numbers of the messages to modify - * @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 & 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 + * @param msgs index set of messages 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 - * @throw exceptions::net_exception if an error occurs - */ - virtual void copyMessages(const folder::path& dest, const std::vector & 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 getMessage(const int num); - std::vector > getMessages(const int from = 1, const int to = -1); - std::vector > getMessages(const std::vector & nums); - - ref getMessageByUID(const message::uid& uid); - std::vector > getMessagesByUID(const std::vector & uids); + std::vector > getMessages(const messageSet& msgs); std::vector 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 & 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 & 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 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 & nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref 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 & 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 & 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. + /** 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 numbers + * @param msgs message set * @param options fetch options * @return fetch request */ static const string buildFetchRequest - (ref cnt, const std::vector & list, const int options); - - /** Construct a fetch request for the specified messages, designated by their UIDs. - * - * @param cnt connection - * @param list list of message UIDs - * @param options fetch options - * @return fetch request - */ - static const string buildFetchRequest - (ref cnt, const std::vector & list, const int options); + (ref 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 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 getMessage(const int num); - std::vector > getMessages(const int from = 1, const int to = -1); - std::vector > getMessages(const std::vector & nums); - - ref getMessageByUID(const message::uid& uid); - std::vector > getMessagesByUID(const std::vector & uids); + std::vector > 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 & 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 & 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 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 & nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref 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 dir); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector 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 +// +// 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 & 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 & 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 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 getMessage(const int num); - std::vector > getMessages(const int from = 1, const int to = -1); - std::vector > getMessages(const std::vector & nums); - - ref getMessageByUID(const message::uid& uid); - std::vector > getMessagesByUID(const std::vector & uids); + std::vector > 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 & 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 & 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 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 & nums); + void copyMessages(const folder::path& dest, const messageSet& msgs); void status(int& count, int& unseen); ref 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 response, std::map & result); + + /** Returns a list of message numbers given a message set. + * + * @param msgs message set + * @return list of message numbers + */ + static const std::vector messageSetToNumberList(const messageSet& msgs); };