mu-query-result: detect thread-subjects
Ongoing... try to determine the thread-subject, to be used in mu4e later.
This commit is contained in:
@ -91,17 +91,26 @@ struct QueryMatch {
|
|||||||
Related = 1 << 1, /**< A related message */
|
Related = 1 << 1, /**< A related message */
|
||||||
Unreadable = 1 << 2, /**< No readable file */
|
Unreadable = 1 << 2, /**< No readable file */
|
||||||
Duplicate = 1 << 3, /**< Message-id seen before */
|
Duplicate = 1 << 3, /**< Message-id seen before */
|
||||||
|
|
||||||
Root = 1 << 10, /**< Is this the thread-root? */
|
Root = 1 << 10, /**< Is this the thread-root? */
|
||||||
First = 1 << 11, /**< Is this the first message in a thread? */
|
First = 1 << 11, /**< Is this the first message in a thread? */
|
||||||
Last = 1 << 12, /**< Is this the last message in a thread? */
|
Last = 1 << 12, /**< Is this the last message in a thread? */
|
||||||
Orphan = 1 << 13, /**< Is this message without a parent? */
|
Orphan = 1 << 13, /**< Is this message without a parent? */
|
||||||
HasChild = 1 << 14 /**< Does this message have a child? */
|
HasChild = 1 << 14, /**< Does this message have a child? */
|
||||||
};
|
|
||||||
|
|
||||||
|
ThreadSubject = 1 << 20, /**< Message holds subject for (sub)thread */
|
||||||
|
};
|
||||||
|
|
||||||
Flags flags{Flags::None}; /**< Flags */
|
Flags flags{Flags::None}; /**< Flags */
|
||||||
std::string sort_key; /**< The main sort-key (for the root level) */
|
std::string sort_key; /**< The main sort-key (for the root level) */
|
||||||
std::string date_key; /**< The date-key (for sorting all sub-root levels) */
|
std::string date_key; /**< The date-key (for sorting all sub-root levels) */
|
||||||
|
// the thread subject is the subject of the first message in a thread,
|
||||||
|
// and any message that has a different subject compared to its predecessor
|
||||||
|
// (ignoring prefixes such as Re:)
|
||||||
|
//
|
||||||
|
// otherwise, it is empty.
|
||||||
|
std::string subject;
|
||||||
|
std::string thread_subject; /**< the thread subject for this message */
|
||||||
size_t thread_level{}; /**< The thread level */
|
size_t thread_level{}; /**< The thread level */
|
||||||
std::string thread_path; /**< The hex-numerial path in the thread, ie. '00:01:0a' */
|
std::string thread_path; /**< The hex-numerial path in the thread, ie. '00:01:0a' */
|
||||||
|
|
||||||
@ -246,6 +255,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
Option<std::string> path() const noexcept { return opt_string(MU_MSG_FIELD_ID_PATH); }
|
Option<std::string> path() const noexcept { return opt_string(MU_MSG_FIELD_ID_PATH); }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file-system path for the document (message) this iterator is
|
||||||
|
* pointing at.
|
||||||
|
*
|
||||||
|
* @return the subject
|
||||||
|
*/
|
||||||
|
Option<std::string> subject() const noexcept { return opt_string(MU_MSG_FIELD_ID_SUBJECT); }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the references for the document (messages) this is iterator is
|
* Get the references for the document (messages) this is iterator is
|
||||||
* pointing at, or empty if pointing at end of if no references are
|
* pointing at, or empty if pointing at end of if no references are
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "mu-query-threads.hh"
|
#include "mu-query-threads.hh"
|
||||||
|
#include "mu-msg-fields.h"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@ -211,6 +212,9 @@ determine_id_table (QueryResultsType& qres, MuMsgFieldId sortfield_id)
|
|||||||
container.query_match->sort_key = mi.opt_string(sortfield_id).value_or("");
|
container.query_match->sort_key = mi.opt_string(sortfield_id).value_or("");
|
||||||
container.query_match->date_key = mi.opt_string(MU_MSG_FIELD_ID_DATE).value_or("");
|
container.query_match->date_key = mi.opt_string(MU_MSG_FIELD_ID_DATE).value_or("");
|
||||||
|
|
||||||
|
// remember the subject, we use it to determine the (sub)thread subject
|
||||||
|
container.query_match->subject = mi.opt_string(MU_MSG_FIELD_ID_SUBJECT).value_or("");
|
||||||
|
|
||||||
// 1.B
|
// 1.B
|
||||||
// For each element in the query_match's References field:
|
// For each element in the query_match's References field:
|
||||||
Container* parent_ref_container{};
|
Container* parent_ref_container{};
|
||||||
@ -389,12 +393,17 @@ determine_root_vec(IdTable& id_table, bool descending)
|
|||||||
return root_vec;
|
return root_vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
update_container_query_match (Container& container, ThreadPathVec& pvec,
|
update_container_query_match (Container& container,
|
||||||
size_t segment_size, bool descending)
|
ThreadPathVec& pvec,
|
||||||
|
size_t segment_size, bool descending,
|
||||||
|
const std::string& prev_subject="")
|
||||||
{
|
{
|
||||||
if (container.is_empty())
|
if (container.is_empty())
|
||||||
return false; // nothing to update.
|
return false; // nothing to update.
|
||||||
|
|
||||||
auto& qmatch{*container.query_match};
|
auto& qmatch{*container.query_match};
|
||||||
|
|
||||||
if (!container.parent)
|
if (!container.parent)
|
||||||
@ -405,6 +414,12 @@ update_container_query_match (Container& container, ThreadPathVec& pvec,
|
|||||||
if (!container.children.empty())
|
if (!container.children.empty())
|
||||||
qmatch.flags |= QueryMatch::Flags::HasChild;
|
qmatch.flags |= QueryMatch::Flags::HasChild;
|
||||||
|
|
||||||
|
// calculate the "thread-subject", which is for UI
|
||||||
|
// purposes.
|
||||||
|
if (qmatch.has_flag(QueryMatch::Flags::Root) ||
|
||||||
|
(qmatch.subject.find(prev_subject) > 5))
|
||||||
|
qmatch.flags |= QueryMatch::Flags::ThreadSubject;
|
||||||
|
|
||||||
if (descending && container.parent) {
|
if (descending && container.parent) {
|
||||||
// trick xapian by giving it "inverse" sorting key so our
|
// trick xapian by giving it "inverse" sorting key so our
|
||||||
// ascending-date sorted threads stay in that order
|
// ascending-date sorted threads stay in that order
|
||||||
@ -421,10 +436,13 @@ update_container_query_match (Container& container, ThreadPathVec& pvec,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sort_siblings (Container::children_type& siblings,
|
sort_siblings (Container::children_type& siblings,
|
||||||
const ThreadPathVec& parent_path_vec,
|
const ThreadPathVec& parent_path_vec,
|
||||||
size_t segment_size, bool descending)
|
size_t segment_size, bool descending,
|
||||||
|
const std::string& last_subject="")
|
||||||
{
|
{
|
||||||
if (siblings.empty())
|
if (siblings.empty())
|
||||||
return;
|
return;
|
||||||
@ -448,13 +466,20 @@ sort_siblings (Container::children_type& siblings,
|
|||||||
last->query_match->flags |= QueryMatch::Flags::Last;
|
last->query_match->flags |= QueryMatch::Flags::Last;
|
||||||
|
|
||||||
size_t idx{0};
|
size_t idx{0};
|
||||||
|
std::string siblings_last_subject{last_subject};
|
||||||
ThreadPathVec thread_path_vec{parent_path_vec};
|
ThreadPathVec thread_path_vec{parent_path_vec};
|
||||||
for (auto&& c: sorted_siblings) {
|
for (auto&& c: sorted_siblings) {
|
||||||
thread_path_vec.emplace_back(idx++);
|
thread_path_vec.emplace_back(idx++);
|
||||||
update_container_query_match (*c, thread_path_vec, segment_size, descending);
|
if (update_container_query_match (*c, thread_path_vec,
|
||||||
|
segment_size, descending,
|
||||||
|
siblings_last_subject)) {
|
||||||
|
siblings_last_subject = c->query_match->subject;
|
||||||
|
}
|
||||||
if (!c->children.empty())
|
if (!c->children.empty())
|
||||||
sort_siblings (c->children, thread_path_vec,
|
sort_siblings (c->children, thread_path_vec,
|
||||||
segment_size, descending);
|
segment_size, descending,
|
||||||
|
siblings_last_subject);
|
||||||
|
|
||||||
thread_path_vec.pop_back();
|
thread_path_vec.pop_back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -747,7 +772,6 @@ test_prune_root_empty_with_child()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_prune_empty_with_children()
|
test_prune_empty_with_children()
|
||||||
{
|
{
|
||||||
@ -765,6 +789,39 @@ test_prune_empty_with_children()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_thread_info_ascending()
|
||||||
|
{
|
||||||
|
// m6 should be nuked
|
||||||
|
auto results = MockQueryResults {
|
||||||
|
MockQueryResult{ "m1", "a", "1", {}},
|
||||||
|
MockQueryResult{ "m2", "b", "2", {}},
|
||||||
|
MockQueryResult{ "m3", "c", "3", {"m2"}},
|
||||||
|
MockQueryResult{ "m4", "d", "4", {"m2"}},
|
||||||
|
|
||||||
|
};
|
||||||
|
calculate_threads(results, MU_MSG_FIELD_ID_DATE, false);
|
||||||
|
|
||||||
|
assert_thread_paths (results, {
|
||||||
|
{ "m1", "0"},
|
||||||
|
{ "m2", "1" },
|
||||||
|
{ "m3", "1:0"},
|
||||||
|
{ "m4", "1:1" },
|
||||||
|
});
|
||||||
|
|
||||||
|
g_assert_true (results[0].query_match().has_flag(
|
||||||
|
QueryMatch::Flags::Root));
|
||||||
|
g_assert_true (results[1].query_match().has_flag(
|
||||||
|
QueryMatch::Flags::Root | QueryMatch::Flags::HasChild));
|
||||||
|
g_assert_true (results[2].query_match().has_flag(
|
||||||
|
QueryMatch::Flags::First));
|
||||||
|
g_assert_true (results[3].query_match().has_flag(
|
||||||
|
QueryMatch::Flags::Last));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char *argv[]) try
|
main (int argc, char *argv[]) try
|
||||||
{
|
{
|
||||||
@ -785,6 +842,8 @@ main (int argc, char *argv[]) try
|
|||||||
test_prune_root_empty_with_child);
|
test_prune_root_empty_with_child);
|
||||||
g_test_add_func ("/threader/prune/prune-empty-with-child",
|
g_test_add_func ("/threader/prune/prune-empty-with-child",
|
||||||
test_prune_empty_with_children);
|
test_prune_empty_with_children);
|
||||||
|
g_test_add_func ("/threader/thread-info/ascending",
|
||||||
|
test_thread_info_ascending);
|
||||||
|
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user