summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2025-03-16 18:38:09 +0200
committerDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2025-04-12 15:07:28 +0700
commita2a1838da49cb87fdeecdc8cb1774736e7398051 (patch)
tree5e1619440b493b79122654ea18ce952cbdeaa613 /lib
parente534f8ae794780d6413a08ee6b8947302b9d138d (diff)
mu-server: cleanup find prop handling
Rework the overly long find-handler. Improve parsing of options, and return better return properties (for use in mu4e).
Diffstat (limited to 'lib')
-rw-r--r--lib/mu-server.cc131
-rw-r--r--lib/utils/mu-sexp.hh5
2 files changed, 94 insertions, 42 deletions
diff --git a/lib/mu-server.cc b/lib/mu-server.cc
index 4aaa0a8..0ea352f 100644
--- a/lib/mu-server.cc
+++ b/lib/mu-server.cc
@@ -640,61 +640,121 @@ Server::Private::output_results(const QueryResults& qres, size_t batch_size) con
return n;
}
-void
-Server::Private::find_handler(const Command& cmd)
+struct FindProps {
+ std::string query;
+ int batch_size{200};
+ int maxnum{-1};
+ Field::Id sort_field_id{Field::Id::Date};
+ QueryFlags flags{QueryFlags::SkipUnreadable};
+};
+// XXX: once we move to C++20, use designated initializers
+
+
+static const std::pair<QueryFlags, std::string> flags_props[] = {
+ { QueryFlags::Descending, ":descending" },
+ { QueryFlags::SkipDuplicates, ":skip-dups" },
+ { QueryFlags::IncludeRelated, ":include-related" },
+ { QueryFlags::Threading, ":threads" }
+};
+
+/**
+ * Determine the find search properties from a command object
+ *
+ * @param cmd the command
+ *
+ * @return structure with find properties
+ */
+static FindProps
+determine_find_props(const Command& cmd)
{
- const auto q{cmd.string_arg(":query").value_or("")};
- const auto threads{cmd.boolean_arg(":threads")};
- // perhaps let mu4e set this as frame-lines of the appropriate frame.
- const auto batch_size{cmd.number_arg(":batch-size").value_or(200)};
- const auto descending{cmd.boolean_arg(":descending")};
- const auto maxnum{cmd.number_arg(":maxnum").value_or(-1) /*unlimited*/};
- const auto skip_dups{cmd.boolean_arg(":skip-dups")};
- const auto include_related{cmd.boolean_arg(":include-related")};
+ FindProps props{};
+
+ props.query = cmd.string_arg(":query").value_or("");
+ props.batch_size = cmd.number_arg(":batch-size").value_or(200);
+ if (props.batch_size < 1)
+ throw Error{Error::Code::InvalidArgument, "invalid batch-size {}", props.batch_size};
+ props.maxnum = cmd.number_arg(":maxnum").value_or(-1) /*unlimited*/;
+ if (props.maxnum < -1)
+ throw Error{Error::Code::InvalidArgument, "invalid max-num {}", props.maxnum};
+
+ const auto threads{cmd.boolean_arg(":threads")};
// complicated!
- auto sort_field_id = std::invoke([&]()->Field::Id {
+ props.sort_field_id = std::invoke([&]()->Field::Id {
if (const auto arg = cmd.symbol_arg(":sortfield"); !arg)
return Field::Id::Date;
else if (arg->length() < 2)
- throw Error{Error::Code::InvalidArgument, "invalid sort field '{}'",
+ throw Error{Error::Code::InvalidArgument, "invalid sort field parameter '{}'",
*arg};
else if (const auto field{field_from_name(arg->substr(1))}; !field)
+ // Note: mu4e gives us ':date' etc., hence the 'substr'
throw Error{Error::Code::InvalidArgument, "invalid sort field '{}'",
*arg};
+ else if (!field->is_sortable())
+ throw Error{Error::Code::InvalidArgument, "not a sortable field '{}'",
+ *arg};
+ else if (threads && field->id != Field::Id::Date)
+ throw Error{Error::Code::InvalidArgument,
+ "with threads, you can only sort by date, not by '{}'", *arg};
else
return field->id;
});
- if (batch_size < 1)
- throw Error{Error::Code::InvalidArgument, "invalid batch-size {}", batch_size};
+ for (const auto& item: flags_props) {
+ if (cmd.boolean_arg(item.second))
+ props.flags |= item.first;
+ }
- auto qflags{QueryFlags::SkipUnreadable}; // don't show unreadables.
+ return props;
+}
+
+/**
+ * Determine the result properties from the find results
+ *
+ * The result properties are passed back to the client
+ *
+ * @param props the find properties
+ *
+ * @return the result properties
+ */
+static Sexp
+determine_result_props(const FindProps& props, size_t found)
+{
Sexp resprops;
- if (descending) {
- qflags |= QueryFlags::Descending;
- resprops.put_props(":reverse", Sexp::t_sym);
- }
- if (skip_dups) {
- qflags |= QueryFlags::SkipDuplicates;
- resprops.put_props(":skip-dups", Sexp::t_sym);
- }
- if (include_related) {
- qflags |= QueryFlags::IncludeRelated;
- resprops.put_props(":include-related", Sexp::t_sym);
- }
- if (threads) {
- qflags |= QueryFlags::Threading;
- resprops.put_props(":threads", Sexp::t_sym);
+
+ resprops.put_props(
+ ":found", found,
+ ":query", props.query,
+ ":query-sexp", parse_query(props.query, false/*!expand*/),
+ ":query-sexp-expanded", parse_query(props.query, true/*expand*/),
+ ":maxnum", props.maxnum,
+ // mu4e expects ':'-prefixed sort-field, for historical reasons.
+ ":sort-field", Sexp::Symbol{mu_format(":{}", field_from_id(props.sort_field_id).name)});
+
+ if (props.maxnum == -1)
+ resprops.put_props(":full", Sexp::t_sym);
+
+ for (const auto& item: flags_props) {
+ if (any_of(props.flags & item.first))
+ resprops.put_props(item.second, Sexp::t_sym);
}
+ return resprops;
+}
+
+void
+Server::Private::find_handler(const Command& cmd)
+{
+ const auto props = determine_find_props(cmd);
+
StopWatch sw{mu_format("{} (indexing: {})", __func__,
indexer().is_running() ? "yes" : "no")};
// we need to _lock_ the store while querying (which likely consists of
// multiple actual queries) + grabbing the results.
std::lock_guard l{store_.lock()};
- auto qres{store_.run_query(q, sort_field_id, qflags, maxnum)};
+ auto qres{store_.run_query(props.query, props.sort_field_id,
+ props.flags, props.maxnum)};
if (!qres)
throw Error(Error::Code::Query, "failed to run query: {}", qres.error().what());
@@ -702,15 +762,10 @@ Server::Private::find_handler(const Command& cmd)
* knows it should erase the headers buffer. this will ensure that the
* output of two finds will not be mixed. */
output_sexp(Sexp().put_props(":erase", Sexp::t_sym));
- const auto bsize{static_cast<size_t>(batch_size)};
+ const auto bsize{static_cast<size_t>(props.batch_size)};
const auto foundnum = output_results(*qres, bsize);
- output_sexp(resprops.put_props(
- ":found", foundnum,
- ":query", q,
- ":query-sexp", parse_query(q, false/*!expand*/).to_string(),
- ":query-sexp-expanded", parse_query(q, true/*expand*/).to_string(),
- ":maxnum", maxnum));
+ output_sexp(determine_result_props(props, foundnum));
}
void
diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh
index 8ffa39b..d25cc64 100644
--- a/lib/utils/mu-sexp.hh
+++ b/lib/utils/mu-sexp.hh
@@ -22,15 +22,12 @@
#include "mu-utils.hh"
-#include <stdexcept>
#include <vector>
#include <string>
#include <string_view>
#include <iostream>
#include <variant>
-#include <cinttypes>
#include <ostream>
-#include <cassert>
#include <utils/mu-result.hh>
#include <utils/mu-option.hh>
@@ -55,7 +52,7 @@ struct Sexp {
Symbol(const std::string& s): name{s} {}
Symbol(std::string&& s): name(std::move(s)) {}
Symbol(const char* str): Symbol(std::string{str}) {}
- Symbol(std::string_view sv): Symbol(std::string{sv}) {}
+ Symbol(const std::string_view& sv): Symbol(std::string{sv}) {}
operator const std::string&() const {return name; }
std::string name;