From 0c05241fecdda3de30cb916f63ba89bf3106ce95 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 11 Aug 2019 20:55:58 -0700 Subject: [PATCH] ircd::m::users: Refactor !users room out of the users:: interface. --- include/ircd/m/users.h | 32 ++++++++- modules/console.cc | 18 +++--- modules/m_users.cc | 144 ++++++++++++++++++++++++++++++++++------- 3 files changed, 161 insertions(+), 33 deletions(-) diff --git a/include/ircd/m/users.h b/include/ircd/m/users.h index 3d233a106..7298814c1 100644 --- a/include/ircd/m/users.h +++ b/include/ircd/m/users.h @@ -13,7 +13,35 @@ namespace ircd::m::users { - bool for_each(const string_view &id_lower_bound, const user::closure_bool &); + struct opts extern const default_opts; + + // Iterate the users + bool for_each(const opts &, const user::closure_bool &); bool for_each(const user::closure_bool &); - void for_each(const user::closure &); + + size_t count(const opts & = default_opts); + bool exists(const opts & = default_opts); } + +/// Shape the query by matching users based on the options filled in. +struct ircd::m::users::opts +{ + /// Fill this in to match the localpart of an mxid. If this is empty then + /// all localparts can be matched. + string_view localpart; + + /// The results match if their localpart startswith the specified localpart. + bool localpart_prefix {false}; + + /// Fill this in to match the hostpart of an mxid, i.e the origin. If this + /// is empty then all servers can be matched. + string_view hostpart; + + /// The results match if their hostpart startswith the specified hostpart + bool hostpart_prefix {false}; + + /// Construction from a single string; decomposes into the options + /// based on ad hoc rules, see definition or use default ctor. + opts(const string_view &query); + opts() = default; +}; diff --git a/modules/console.cc b/modules/console.cc index 16314d394..07d8fc51d 100644 --- a/modules/console.cc +++ b/modules/console.cc @@ -11152,23 +11152,25 @@ console_cmd__users(opt &out, const string_view &line) { const params param{line, " ", { - "prefix" + "query" }}; - const auto prefix + const auto query { - param.at("prefix", string_view{}) + param.at("query", string_view{}) }; - m::users::for_each(prefix, m::user::closure_bool{[&out, &prefix] + const m::users::opts opts + { + query + }; + + m::users::for_each(opts, [&out] (const m::user &user) { - if(prefix && !startswith(user.user_id, prefix)) - return false; - out << user.user_id << std::endl; return true; - }}); + }); return true; } diff --git a/modules/m_users.cc b/modules/m_users.cc index 1d2564bb0..5f429b4a7 100644 --- a/modules/m_users.cc +++ b/modules/m_users.cc @@ -8,49 +8,147 @@ // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. +namespace ircd::m::users +{ + static bool for_each_host(const opts &, const user::closure_bool &); + static bool for_each_in_host(const opts &, const user::closure_bool &); +} + ircd::mapi::header IRCD_MODULE { "Matrix users interface" }; -void +decltype(ircd::m::users::default_opts) +ircd::m::users::default_opts; + +bool IRCD_MODULE_EXPORT -ircd::m::users::for_each(const user::closure &closure) +ircd::m::users::exists(const opts &opts) { - for_each(user::closure_bool{[&closure] - (const m::user &user) + return !for_each(opts, [] + (const auto &) { - closure(user); + // return false to break and have for_each() returns false + return false; + }); +} + +size_t +IRCD_MODULE_EXPORT +ircd::m::users::count(const opts &opts) +{ + size_t ret(0); + for_each(opts, [&ret](const auto &) + { + ++ret; return true; - }}); + }); + + return ret; } bool IRCD_MODULE_EXPORT ircd::m::users::for_each(const user::closure_bool &closure) { - return for_each(string_view{}, closure); + return for_each(default_opts, closure); } bool IRCD_MODULE_EXPORT -ircd::m::users::for_each(const string_view &lower_bound, +ircd::m::users::for_each(const opts &opts, const user::closure_bool &closure) { - const m::room::state state - { - user::users - }; - - return state.for_each("ircd.user", lower_bound, m::room::state::keys_bool{[&closure] - (const string_view &user_id) - { - const m::user &user - { - user_id - }; - - return closure(user); - }}); + // Note: if opts.hostpart is given then for_each_host() will close over + // that host, so no branch is needed here. + return for_each_host(opts, closure); +} + +bool +ircd::m::users::for_each_host(const opts &opts, + const user::closure_bool &closure) +{ + bool ret{true}; + events::for_each_origin(opts.hostpart, [&ret, &opts, &closure] + (const string_view &origin) + { + // The events:: iteration interface works with prefixes; if our + // user wants to iterate users on exactly a single server, we test + // for an exact match here and can break from the loop if no match. + if(opts.hostpart && !opts.hostpart_prefix) + if(origin != opts.hostpart) + return false; + + auto _opts(opts); + _opts.hostpart = origin; + ret = for_each_in_host(_opts, closure); + return ret; + }); + + return ret; +} + +bool +ircd::m::users::for_each_in_host(const opts &opts, + const user::closure_bool &closure) +{ + assert(opts.hostpart); + + bool ret{true}; + events::for_each_sender(opts.hostpart, [&opts, &ret, &closure] + (const id::user &sender) + { + // The events:: iteration interface only tests if the sender's + // hostpart startswith our queried hostpart; we want an exact + // match here, otherwise our loop can finish. + if(sender.host() != opts.hostpart) + return false; + + // Skip this entry if the user wants a prefix match on the localpart + // and this mxid doesn't match. + if(opts.localpart && opts.localpart_prefix) + if(!startswith(sender.local(), opts.localpart)) + return true; + + // Skip this entry if the user wants an exact match on the localpart + // and this mxid doesn't match. + if(opts.localpart && !opts.localpart_prefix) + if(sender.local() != opts.localpart) + return true; + + // Call the user with match. + ret = closure(sender); + return ret; + }); + + return ret; +} + +IRCD_MODULE_EXPORT +ircd::m::users::opts::opts(const string_view &query) +{ + if(startswith(query, '@') && has(query, ':')) + { + localpart = split(query, ':').first; + hostpart = split(query, ':').second; + return; + } + + if(startswith(query, '@')) + { + localpart = query; + localpart_prefix = true; + return; + } + + if(startswith(query, ':')) + { + hostpart = lstrip(query, ':'); + return; + } + + hostpart = query; + hostpart_prefix = true; }