mirror of https://github.com/djcb/mu.git
lib/server: send query results in batches
Instead of one message (header) at a time, send batches of them; this allows for much faster handling in mu4e.
This commit is contained in:
parent
e46347aa54
commit
f17995b113
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2020-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
**
|
**
|
||||||
** This program is free software; you can redistribute it and/or modify it
|
** 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
|
** under the terms of the GNU General Public License as published by the
|
||||||
|
@ -104,7 +104,7 @@ struct Server::Private {
|
||||||
void sent_handler(const Parameters& params);
|
void sent_handler(const Parameters& params);
|
||||||
void view_handler(const Parameters& params);
|
void view_handler(const Parameters& params);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// helpers
|
// helpers
|
||||||
Sexp build_message_sexp(MuMsg* msg,
|
Sexp build_message_sexp(MuMsg* msg,
|
||||||
unsigned docid,
|
unsigned docid,
|
||||||
|
@ -132,13 +132,12 @@ struct Server::Private {
|
||||||
};
|
};
|
||||||
|
|
||||||
static Sexp
|
static Sexp
|
||||||
build_metadata(const QueryMatch& qmatch, unsigned docid)
|
build_metadata(const QueryMatch& qmatch)
|
||||||
{
|
{
|
||||||
Sexp::List mdata;
|
Sexp::List mdata;
|
||||||
|
|
||||||
auto symbol_t = [] { return Sexp::make_symbol("t"); };
|
auto symbol_t = [] { return Sexp::make_symbol("t"); };
|
||||||
|
|
||||||
mdata.add_prop(":docid", Sexp::make_number(docid));
|
|
||||||
mdata.add_prop(":path", Sexp::make_string(qmatch.thread_path));
|
mdata.add_prop(":path", Sexp::make_string(qmatch.thread_path));
|
||||||
mdata.add_prop(":level", Sexp::make_number(qmatch.thread_level));
|
mdata.add_prop(":level", Sexp::make_number(qmatch.thread_level));
|
||||||
mdata.add_prop(":date", Sexp::make_string(qmatch.thread_date));
|
mdata.add_prop(":date", Sexp::make_string(qmatch.thread_date));
|
||||||
|
@ -170,12 +169,9 @@ build_metadata(const QueryMatch& qmatch, unsigned docid)
|
||||||
return Sexp::make_list(std::move(mdata));
|
return Sexp::make_list(std::move(mdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* a full message-sexp consists looks something like:
|
/*
|
||||||
* (:meta (<thread-info etc>) :message (<mesage-info>))
|
* A message here is a Sexp::List consists of a message s-expression with
|
||||||
*
|
* optionally a :meta expression added.
|
||||||
* before, we stuffed the meta-data in the message; however,
|
|
||||||
* keeping the message 'pristine' allows us to cache the message
|
|
||||||
* sexps (TBI).
|
|
||||||
*/
|
*/
|
||||||
Sexp
|
Sexp
|
||||||
Server::Private::build_message_sexp(MuMsg* msg,
|
Server::Private::build_message_sexp(MuMsg* msg,
|
||||||
|
@ -183,13 +179,11 @@ Server::Private::build_message_sexp(MuMsg* msg,
|
||||||
const Option<QueryMatch&> qm,
|
const Option<QueryMatch&> qm,
|
||||||
MuMsgOptions opts) const
|
MuMsgOptions opts) const
|
||||||
{
|
{
|
||||||
Sexp::List msexp;
|
auto msgsexp{Mu::msg_to_sexp_list(msg, docid, opts)};
|
||||||
|
|
||||||
msexp.add_prop(":message", Sexp::make_list(Mu::msg_to_sexp_list(msg, docid, opts)));
|
|
||||||
if (qm)
|
if (qm)
|
||||||
msexp.add_prop(":meta", build_metadata(*qm, docid));
|
msgsexp.add_prop(":meta", build_metadata(*qm));
|
||||||
|
|
||||||
return Sexp::make_list(std::move(msexp));
|
return Sexp::make_list(std::move(msgsexp));
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandMap
|
CommandMap
|
||||||
|
@ -617,20 +611,37 @@ determine_docids(const Query& q, const Parameters& params)
|
||||||
size_t
|
size_t
|
||||||
Server::Private::output_results(const QueryResults& qres, size_t batch_size) const
|
Server::Private::output_results(const QueryResults& qres, size_t batch_size) const
|
||||||
{
|
{
|
||||||
size_t n{};
|
size_t n{};
|
||||||
|
Sexp::List headers;
|
||||||
|
|
||||||
|
const auto output_batch = [&](Sexp::List&& hdrs) {
|
||||||
|
Sexp::List batch;
|
||||||
|
batch.add_prop(":headers", Sexp::make_list(std::move(hdrs)));
|
||||||
|
output_sexp(std::move(batch));
|
||||||
|
};
|
||||||
|
|
||||||
for (auto&& mi : qres) {
|
for (auto&& mi : qres) {
|
||||||
auto msg{mi.floating_msg()};
|
auto msg{mi.floating_msg()};
|
||||||
if (!msg)
|
if (!msg)
|
||||||
continue;
|
continue;
|
||||||
++n;
|
++n;
|
||||||
auto qm{mi.query_match()};
|
|
||||||
Sexp::List lst;
|
// construct sexp for a single header.
|
||||||
lst.add_prop(":header",
|
auto qm{mi.query_match()};
|
||||||
build_message_sexp(msg, mi.doc_id(), qm, MU_MSG_OPTION_HEADERS_ONLY));
|
headers.add(build_message_sexp(msg, mi.doc_id(), qm, MU_MSG_OPTION_HEADERS_ONLY));
|
||||||
output_sexp(std::move(lst));
|
// we output up-to-batch-size lists of messages. It's much
|
||||||
|
// faster (on the emacs side) to handle such batches than single
|
||||||
|
// headers.
|
||||||
|
if (headers.size() % batch_size == 0) {
|
||||||
|
output_batch(std::move(headers));
|
||||||
|
headers.clear();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remaining.
|
||||||
|
if (!headers.empty())
|
||||||
|
output_batch(std::move(headers));
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,7 +650,8 @@ Server::Private::find_handler(const Parameters& params)
|
||||||
{
|
{
|
||||||
const auto q{get_string_or(params, ":query")};
|
const auto q{get_string_or(params, ":query")};
|
||||||
const auto threads{get_bool_or(params, ":threads", false)};
|
const auto threads{get_bool_or(params, ":threads", false)};
|
||||||
const auto batch_size{get_int_or(params, ":batch-size", 60)};
|
// perhaps let mu4e set this as frame-lines of the appropriate frame.
|
||||||
|
const auto batch_size{get_int_or(params, ":batch-size", 110)};
|
||||||
const auto sortfieldstr{get_symbol_or(params, ":sortfield", "")};
|
const auto sortfieldstr{get_symbol_or(params, ":sortfield", "")};
|
||||||
const auto descending{get_bool_or(params, ":descending", false)};
|
const auto descending{get_bool_or(params, ":descending", false)};
|
||||||
const auto maxnum{get_int_or(params, ":maxnum", -1 /*unlimited*/)};
|
const auto maxnum{get_int_or(params, ":maxnum", -1 /*unlimited*/)};
|
||||||
|
@ -820,8 +832,8 @@ Server::Private::perform_move(Store::Id docid,
|
||||||
if (!mu_msg_move_to_maildir(msg, maildir.c_str(), flags, TRUE, new_name, &gerr))
|
if (!mu_msg_move_to_maildir(msg, maildir.c_str(), flags, TRUE, new_name, &gerr))
|
||||||
throw Error{Error::Code::File, &gerr, "failed to move message"};
|
throw Error{Error::Code::File, &gerr, "failed to move message"};
|
||||||
|
|
||||||
/* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir fields
|
/* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir
|
||||||
* will be updated as wel */
|
* fields will be updated as wel */
|
||||||
if (!store_.update_message(msg, docid))
|
if (!store_.update_message(msg, docid))
|
||||||
throw Error{Error::Code::Store, "failed to store updated message"};
|
throw Error{Error::Code::Store, "failed to store updated message"};
|
||||||
|
|
||||||
|
@ -1037,8 +1049,8 @@ Server::Private::maybe_mark_as_read(MuMsg* msg, Store::Id docid)
|
||||||
&gerr))
|
&gerr))
|
||||||
throw Error{Error::Code::File, &gerr, "failed to move message"};
|
throw Error{Error::Code::File, &gerr, "failed to move message"};
|
||||||
|
|
||||||
/* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir fields
|
/* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir
|
||||||
* will be updated as wel */
|
* fields will be updated as wel */
|
||||||
if (!store().update_message(msg, docid))
|
if (!store().update_message(msg, docid))
|
||||||
throw Error{Error::Code::Store, "failed to store updated message"};
|
throw Error{Error::Code::Store, "failed to store updated message"};
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,11 @@ struct Sexp {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all elements from the list.
|
||||||
|
*/
|
||||||
|
void clear() { seq_.clear(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of elements in the list
|
* Get the number of elements in the list
|
||||||
*
|
*
|
||||||
|
@ -192,7 +197,7 @@ struct Sexp {
|
||||||
*/
|
*/
|
||||||
size_t empty() const { return seq_.empty(); }
|
size_t empty() const { return seq_.empty(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct Sexp;
|
friend struct Sexp;
|
||||||
Seq seq_;
|
Seq seq_;
|
||||||
};
|
};
|
||||||
|
@ -292,7 +297,7 @@ struct Sexp {
|
||||||
return is_prop_list(list().begin() + 1, list().end());
|
return is_prop_list(list().begin() + 1, list().end());
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)}
|
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)}
|
||||||
{
|
{
|
||||||
if (is_list())
|
if (is_list())
|
||||||
|
|
Loading…
Reference in New Issue