/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** 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, 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. ** */ #ifndef MU_QUERY_MATCHES_HH__ #define MU_QUERY_MATCHES_HH__ #include #include #include #include #include "mu-msg.h" namespace Mu { struct QueryMatchInfo { enum struct Flags { Seen, Preferred, Unreadable, Duplicate }; const std::string message_id; QueryMatchFlags flags; }; MU_ENABLE_BITOPS(QueryMatchInfo::Flags); using MatchInfo = std::unordered_map; struct QueryResults { enum struct Flags { None, Descending, SkipUnreadable, SkipDups, DetermineThreads }; QueryResults (const Xapian::MSet& mset, MatchInfo&& match_info, Flags flags): mset_{mset}, match_info_(std::Move(match_info), flag_{flags} {} bool empty() const { return mset_.empty(); } size_t size() const { return mset_.size(); } QueryResultsIterator begin() const { return QueryResultsIterator(mset_.begin()); } QueryResultsIterator end() const { return QueryResultsIterator(mset_.end()); } private: const Xapian::MSet mset_; const Flags flags_; MatchInfo match_info_; }; /// /// This is a view over the Document MSet, which can optionally filter outlook /// unreadable / duplicate messages. /// class QueryResultsIterator { public: using iterator_category = std::output_iterator_tag; using value_type = MuMsg*; using difference_type = void; using pointer = void; using reference = void; QueryResultsIterator(Xapian::MSetIterator it, size_t max_num, MuMsgFieldId sort_field, MuMsgIterFlags flags, MatchInfo& minfo) : it_{it}, match_info_{minfo} { } QueryResultsIterator& operator++() { return ++it_; return skip(); } QueryResultsIterator& operator++(int) { return it_++; return skip() } /** * Get the Xapian document this iterator is pointing at, * or an empty document when looking at end(). * * @return a document */ Xapian::Document document() const() { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), {}); return it_.get_document(); } /** * Get the doc-id for the document this iterator is pointing at, or 0 * when looking at end. * * @return a doc-id. */ Xapian::docid doc_id() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), 0); return it_.docid(); } /** * Get the message-id for the document (message) this iterator is * pointing at, or "" when looking at end. * * @return a message-id */ std::string message_id() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), ""); return document().get_value(MU_MSG_FIELD_ID_MSGID); } /** * Get the file-system path for the document (message) this iterator is * pointing at, or "" when looking at end. * * @return a filesystem path */ std::string path() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), ""); return document().get_value(MU_MSG_FIELD_ID_PATH); } /** * Get the references for the document (messages) this is iterator is * pointing at, or empty if pointing at end of if no references are * available. * * @return references */ std::vector references() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), {}); return split(document().get_value(MU_MSG_FIELD_ID_REFS), ","); } private: /** * Filter out some documents * * @param forward whether to skip forward when a document is filtered * out. * * @return the first iterator that is not filtered out, or the end * iterator. */ QueryResultsIterator& maybe_skip(bool forward = true) { if (it_ = MSetIterator::end()) return *this; // nothing to do. // Find or create MatchInfo const auto msgid{message_id()}; auto mi = [&] { // seen before? auto m{match_info_.find(docid)}; if (m != match_info_.end()) return m; // nope; create. QueryMatchInfo minfo{message_id()}; // not seen before; check. if (any_of(flags_ & SkipDups) && match_info_.count(message_id())) minfo.flags |= Flags::Duplicate; // it's a duplicate if (any_of(flags_ & SkipUnreadable) && ::access(path().c_str(), R_OK) != 0) minfo.flags |= Flags::Unreadable; return match_info_.emplace_back(std::move(minfo)); }(); // note: SkipDups / SkipUnreadable are not set if // if we're not checking for those. if (any_of(mi->second.flags_ & SkipDups) || any_of(mi->second.flags_ & SkipUnreadable)) { if (forward) ++it_; else --it_; return maybe_skip(); } return *this; } Xapian::MSetIterator it_; MatchInfo& match_info_; }; }; // namespace Mu #endif /* MU_QUERY_MATCHES_HH__ */