0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-05-29 00:03:45 +02:00

ircd:Ⓜ️:rooms: Refactor interface; split rooms::summary; split module.

This commit is contained in:
Jason Volk 2019-08-12 22:59:27 -07:00
parent f5b45f32d0
commit 84e79a64e6
13 changed files with 654 additions and 632 deletions

View file

@ -16,67 +16,75 @@
///
namespace ircd::m::rooms
{
struct each_opts;
struct opts extern const opts_default;
bool is_public(const room::id &);
size_t count_public(const string_view &server = {});
// Iterate the rooms
bool for_each(const opts &, const room::id::closure_bool &);
bool for_each(const room::id::closure_bool &);
bool for_each(const each_opts &);
template<class... args> bool for_each(args&&...);
// Linkage to utils that build a publicrooms summary from room state.
void summary_chunk(const m::room &, json::stack::object &chunk);
json::object summary_chunk(const m::room &, const mutable_buffer &out);
event::id::buf summary_set(const m::room::id &, const json::object &summary);
event::id::buf summary_set(const m::room &);
event::id::buf summary_del(const m::room &);
std::pair<size_t, std::string> fetch_update(const net::hostport &, const string_view &since = {}, const size_t &limit = 64);
// tools
size_t count(const opts & = opts_default);
bool has(const opts & = opts_default);
}
/// Arguments structure to rooms::for_each(). This reduces the API surface to
/// handle a rich set of ways to iterate over the rooms. Several convenience
/// constructors based on common usage are provided; note that these are not
/// the only options patterns.
struct ircd::m::rooms::each_opts
/// Interface to tools that build a publicrooms summary from room state.
namespace ircd::m::rooms::summary
{
/// If set, seek to this room_id or lower-bound to the closest room_id.
string_view key;
struct fetch;
/// All public rooms only; key may be a server hostname or room_id.
/// - for server hostname: iterates known rooms from that server.
/// - for room_id: starts iteration from that (or closest) room_id
/// which can be used as a since/pagination token.
bool public_rooms {false};
// observers
bool has(const room::id &);
void chunk(const room &, json::stack::object &chunk);
json::object chunk(const room &, const mutable_buffer &out);
/// Principal closure for results; invoked synchronously during the call;
/// for-each protocol: return true to continue, false to break.
room::id::closure_bool closure;
// mutators
event::id::buf set(const room::id &, const json::object &summary);
event::id::buf set(const room &);
event::id::buf del(const room &);
}
each_opts(room::id::closure_bool);
each_opts(const string_view &key, room::id::closure_bool);
each_opts() = default;
struct ircd::m::rooms::summary::fetch
{
// conf
static conf::item<size_t> limit;
static conf::item<seconds> timeout;
// result
size_t total_room_count_estimate {0};
std::string next_batch;
// request
fetch(const net::hostport &hp,
const string_view &since = {},
const size_t &limit = 64);
fetch() = default;
};
inline
ircd::m::rooms::each_opts::each_opts(room::id::closure_bool closure)
:closure{std::move(closure)}
{}
inline
ircd::m::rooms::each_opts::each_opts(const string_view &key,
room::id::closure_bool closure)
:key{key}
,closure{std::move(closure)}
{}
template<class... args>
bool
ircd::m::rooms::for_each(args&&... a)
/// Arguments structure to rooms::for_each(). This reduces the API surface to
/// handle a rich set of ways to iterate over the rooms.
struct ircd::m::rooms::opts
{
const each_opts opts
{
std::forward<args>(a)...
};
/// A full or partial room_id can be defined; partial is only valid if
/// lower_bound is true.
string_view room_id;
return for_each(opts);
}
/// Set a string for the join_rule; undefined matches all. For example,
/// if set to "join" then the iteration can list public rooms.
string_view join_rule;
/// Set a string to localize query to a single server
string_view server;
/// Spec search term
string_view search_term;
/// Filters results to those that have a public rooms list summary
bool summary {false};
/// Indicates if the interface treats the room_id specified as a lower
/// bound rather than exact match. This means an iteration will start
/// at the same or next key, and continue indefinitely. By false default,
/// when a room_id is given a for_each() will have 0 or 1 iterations.
bool lower_bound {false};
};

125
ircd/m.cc
View file

@ -204,6 +204,7 @@ ircd::m::module_names
"m_feds",
"m_events",
"m_rooms",
"m_rooms_summary",
"m_users",
"m_user",
"m_user_rooms",
@ -2407,130 +2408,6 @@ ircd::m::event_filter::event_filter(const mutable_buffer &buf,
{
}
///////////////////////////////////////////////////////////////////////////////
//
// m/rooms.h
//
ircd::m::event::id::buf
ircd::m::rooms::summary_del(const m::room &r)
{
using prototype = event::id::buf (const m::room &);
static mods::import<prototype> call
{
"m_rooms", "ircd::m::rooms::summary_del"
};
return call(r);
}
ircd::m::event::id::buf
ircd::m::rooms::summary_set(const m::room &room)
{
if(!exists(room))
throw m::NOT_FOUND
{
"Cannot set a summary for room '%s' which I have no state for",
string_view{room.room_id}
};
const unique_buffer<mutable_buffer> buf
{
48_KiB
};
const json::object summary
{
summary_chunk(room, buf)
};
return summary_set(room.room_id, summary);
}
ircd::m::event::id::buf
ircd::m::rooms::summary_set(const m::room::id &room_id,
const json::object &summary)
{
using prototype = event::id::buf (const m::room::id &, const json::object &);
static mods::import<prototype> function
{
"m_rooms", "ircd::m::rooms::summary_set"
};
return function(room_id, summary);
}
ircd::json::object
ircd::m::rooms::summary_chunk(const m::room &room,
const mutable_buffer &buf)
{
json::stack out{buf};
{
json::stack::object obj{out};
summary_chunk(room, obj);
}
return json::object
{
out.completed()
};
}
void
ircd::m::rooms::summary_chunk(const m::room &room,
json::stack::object &chunk)
{
using prototype = void (const m::room &, json::stack::object &);
static mods::import<prototype> function
{
"m_rooms", "ircd::m::rooms::summary_chunk"
};
return function(room, chunk);
}
bool
ircd::m::rooms::for_each(const each_opts &opts)
{
using prototype = bool (const each_opts &);
static mods::import<prototype> call
{
"m_rooms", "ircd::m::rooms::for_each"
};
return call(opts);
}
bool
ircd::m::rooms::is_public(const room::id &room_id)
{
using prototype = bool (const room::id &);
static mods::import<prototype> call
{
"m_rooms", "ircd::m::rooms::is_public"
};
return call(room_id);
}
size_t
ircd::m::rooms::count_public(const string_view &server)
{
using prototype = size_t (const string_view &);
static mods::import<prototype> function
{
"m_rooms", "ircd::m::rooms::count_public"
};
return function(server);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/user.h

View file

@ -123,6 +123,7 @@ m_ignored_user_list_la_SOURCES = m_ignored_user_list.cc
m_breadcrumb_rooms_la_SOURCES = m_breadcrumb_rooms.cc
m_events_la_SOURCES = m_events.cc
m_rooms_la_SOURCES = m_rooms.cc
m_rooms_summary_la_SOURCES = m_rooms_summary.cc
m_room_timeline_la_SOURCES = m_room_timeline.cc
m_room_create_la_SOURCES = m_room_create.cc
m_room_member_la_SOURCES = m_room_member.cc
@ -166,6 +167,7 @@ m_module_LTLIBRARIES = \
m_breadcrumb_rooms.la \
m_events.la \
m_rooms.la \
m_rooms_summary.la \
m_room_timeline.la \
m_room_create.la \
m_room_member.la \

View file

@ -401,7 +401,7 @@ try
// This call sends a message to the !public room to list this room in the
// public rooms list. We set an empty summary for this room because we
// already have its state on this server;
m::rooms::summary_set(room.room_id, json::object{});
m::rooms::summary::set(room.room_id, json::object{});
}
catch(const std::exception &e)
{

View file

@ -100,11 +100,11 @@ put__list_appservice(client &client,
case "public"_:
// We set an empty summary for this room because
// we already have its state on this server;
m::rooms::summary_set(room.room_id, json::object{});
m::rooms::summary::set(room.room_id, json::object{});
break;
case "private"_:
m::rooms::summary_del(room.room_id);
m::rooms::summary::del(room.room_id);
break;
default: throw m::UNSUPPORTED

View file

@ -98,11 +98,11 @@ put__list_room(client &client,
case "public"_:
// We set an empty summary for this room because
// we already have its state on this server;
m::rooms::summary_set(room.room_id, json::object{});
m::rooms::summary::set(room.room_id, json::object{});
break;
case "private"_:
m::rooms::summary_del(room.room_id);
m::rooms::summary::del(room.room_id);
break;
default: throw m::UNSUPPORTED
@ -153,7 +153,7 @@ get__list_room(client &client,
const string_view &visibility
{
m::rooms::is_public(room)? "public" : "private"
m::rooms::summary::has(room_id)? "public" : "private"
};
return resource::response

View file

@ -52,7 +52,7 @@ resource::response
get__publicrooms(client &client,
const resource::request &request)
{
char since_buf[256];
char since_buf[m::room::id::buf::SIZE];
const string_view &since
{
request.has("since")?
@ -96,7 +96,10 @@ get__publicrooms(client &client,
if(server && !my_host(server)) try
{
m::rooms::fetch_update(server, since, limit);
m::rooms::summary::fetch
{
server, since, limit
};
}
catch(const std::exception &e)
{
@ -118,6 +121,14 @@ get__publicrooms(client &client,
response.buf, response.flusher(), size_t(flush_hiwat)
};
m::rooms::opts opts;
opts.join_rule = "public";
opts.summary = true;
opts.search_term = search_term;
opts.server = server?: my_host();
opts.lower_bound = true;
opts.room_id = since;
size_t count{0};
m::room::id::buf prev_batch_buf;
m::room::id::buf next_batch_buf;
@ -125,42 +136,26 @@ get__publicrooms(client &client,
{
json::stack::member chunk_m{top, "chunk"};
json::stack::array chunk{chunk_m};
//TODO: better keying for search terms
const string_view &key
{
since?
since:
server?
server:
search_term?
search_term:
my_host()
};
m::rooms::each_opts opts;
opts.public_rooms = true;
opts.key = key;
opts.closure = [&](const m::room::id &room_id)
m::rooms::for_each(opts, [&](const m::room::id &room_id)
{
json::stack::object obj{chunk};
m::rooms::summary_chunk(room_id, obj);
m::rooms::summary::chunk(room_id, obj);
if(!count && !empty(since))
prev_batch_buf = room_id; //TODO: ???
next_batch_buf = room_id;
return ++count < limit;
};
m::rooms::for_each(opts);
});
}
// To count the total we clear the since token, otherwise the count
// will be the remainder.
opts.room_id = {};
json::stack::member
{
top, "total_room_count_estimate", json::value
{
ssize_t(m::rooms::count_public(server))
ssize_t(m::rooms::count(opts))
}
};

View file

@ -108,7 +108,7 @@ get__initialsync_local(client &client,
json::stack::member
{
out, "visibility", m::rooms::is_public(room)? "public"_sv: "private"_sv
out, "visibility", m::rooms::summary::has(room)? "public"_sv: "private"_sv
};
const m::user::room_account_data room_account_data

View file

@ -7566,11 +7566,45 @@ console_cmd__eval__file(opt &out, const string_view &line)
bool
console_cmd__rooms(opt &out, const string_view &line)
{
m::rooms::for_each([&out]
(const m::room::id &room_id)
const params param{line, " ",
{
"server", "search_term", "limit"
}};
const string_view &server
{
param["server"] != "*"?
param["server"]:
string_view{}
};
const string_view &search_term
{
param["server"] && startswith(param["server"], ':') && param["search_term"] != "*"?
param["search_term"]:
param["server"] && startswith(param["server"], ':')?
string_view{}:
param["server"] != "*"?
param["server"]:
string_view{}
};
auto limit
{
param.at("limit", 32L)
};
m::rooms::opts opts;
opts.server = server;
opts.search_term = search_term;
m::rooms::for_each(opts, [&limit, &out]
(const m::room::id &room_id) -> bool
{
out << room_id << std::endl;
return true;
return --limit > 0;
});
return true;
@ -7581,12 +7615,26 @@ console_cmd__rooms__public(opt &out, const string_view &line)
{
const params param{line, " ",
{
"server|room_id_lb", "limit"
"server", "search_term", "limit"
}};
const string_view &key
const string_view &server
{
param.at("server|room_id_lb", string_view{})
param.at("server", string_view{})
};
const string_view &search_term
{
param["server"] && startswith(param["server"], ':') && param["search_term"] != "*"?
param["search_term"]:
param["server"] && startswith(param["server"], ':')?
string_view{}:
param["server"] != "*"?
param["server"]:
string_view{}
};
auto limit
@ -7594,17 +7642,18 @@ console_cmd__rooms__public(opt &out, const string_view &line)
param.at("limit", 32L)
};
m::rooms::each_opts opts;
opts.public_rooms = true;
opts.key = key;
opts.closure = [&limit, &out]
m::rooms::opts opts;
opts.server = server;
opts.search_term = search_term;
opts.summary = true;
opts.join_rule = "public";
m::rooms::for_each(opts, [&limit, &out]
(const m::room::id &room_id) -> bool
{
out << room_id << std::endl;
return --limit > 0;
};
});
m::rooms::for_each(opts);
return true;
}
@ -7626,14 +7675,14 @@ console_cmd__rooms__fetch(opt &out, const string_view &line)
param.at("since", string_view{})
};
const auto pair
const m::rooms::summary::fetch fetch
{
m::rooms::fetch_update(server, since)
server, since
};
out << "done" << std::endl
<< "total room count estimate: " << pair.first << std::endl
<< "next batch: " << pair.second << std::endl
<< "total room count estimate: " << fetch.total_room_count_estimate << std::endl
<< "next batch: " << fetch.next_batch << std::endl
;
return true;

View file

@ -51,9 +51,10 @@ resource::response
handle_get(client &client,
const resource::request &request)
{
char sincebuf[m::room::id::buf::SIZE];
const string_view &since
{
request.query["since"]
url::decode(sincebuf, request.query["since"])
};
if(since && !valid(m::id::ROOM, since))
@ -90,6 +91,13 @@ handle_get(client &client,
response.buf, response.flusher(), size_t(flush_hiwat)
};
m::rooms::opts opts;
opts.summary = true;
opts.join_rule = "public";
opts.server = my_host();
opts.lower_bound = true;
opts.room_id = since;
size_t count{0};
m::room::id::buf prev_batch_buf;
m::room::id::buf next_batch_buf;
@ -100,30 +108,25 @@ handle_get(client &client,
top, "chunk"
};
const string_view &key
m::rooms::for_each(opts, [&]
(const m::room::id &room_id)
{
since?: my_host()
};
json::stack::object obj
{
chunk
};
m::rooms::each_opts opts;
opts.public_rooms = true;
opts.key = key;
opts.closure = [&](const m::room::id &room_id)
{
json::stack::object obj{chunk};
m::rooms::summary_chunk(room_id, obj);
m::rooms::summary::chunk(room_id, obj);
next_batch_buf = room_id;
return ++count < limit;
};
m::rooms::for_each(opts);
});
}
json::stack::member
{
top, "total_room_count_estimate", json::value
{
ssize_t(m::rooms::count_public(my_host()))
ssize_t(m::rooms::count(opts))
}
};

View file

@ -42,7 +42,7 @@ _changed_rules(const m::event &event,
// public rooms list. We set an empty summary for this room because we
// already have its state on this server; saving a summary object in the
// event sent to !public is only for rooms whose state is not synced.
m::rooms::summary_set(room_id, json::object{});
m::rooms::summary::set(room_id, json::object{});
}
m::hookfn<m::vm::eval &>

View file

@ -8,443 +8,110 @@
// 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::rooms
{
static string_view make_state_key(const mutable_buffer &out, const m::room::id &);
static m::room::id::buf unmake_state_key(const string_view &);
static void remote_summary_chunk(const m::room &room, json::stack::object &obj);
static void local_summary_chunk(const m::room &room, json::stack::object &obj);
static bool for_each_public(const string_view &room_id_lb, const room::id::closure_bool &);
extern conf::item<size_t> fetch_limit;
extern conf::item<seconds> fetch_timeout;
extern m::hookfn<vm::eval &> create_public_room;
extern const room::id::buf public_room_id;
}
ircd::mapi::header
IRCD_MODULE
{
"Matrix rooms interface; modular components"
};
decltype(ircd::m::rooms::public_room_id)
ircd::m::rooms::public_room_id
{
"public", ircd::my_host()
};
/// Create the public rooms room during initial database bootstrap.
/// This hooks the creation of the !ircd room which is a fundamental
/// event indicating the database has just been created.
decltype(ircd::m::rooms::create_public_room)
ircd::m::rooms::create_public_room
{
{
{ "_site", "vm.effect" },
{ "room_id", "!ircd" },
{ "type", "m.room.create" },
},
[](const m::event &, m::vm::eval &)
{
m::create(public_room_id, m::me.user_id);
}
};
decltype(ircd::m::rooms::opts_default)
ircd::m::rooms::opts_default;
bool
IRCD_MODULE_EXPORT
ircd::m::rooms::for_each(const each_opts &opts)
ircd::m::rooms::has(const opts &opts)
{
if(opts.public_rooms)
return for_each_public(opts.key, opts.closure);
const room::state state
return !for_each(opts, []
(const m::room::id &)
{
my_room
};
const room::state::keys_bool keys{[&opts]
(const string_view &room_id) -> bool
{
return opts.closure(room_id);
}};
return state.for_each("ircd.room", opts.key, keys);
}
bool
IRCD_MODULE_EXPORT
ircd::m::rooms::is_public(const room::id &room_id)
{
const m::room room{public_room_id};
const m::room::state state{room};
return state.has("ircd.room", room_id);
// false to break; for_each() returns false
return false;
});
}
size_t
IRCD_MODULE_EXPORT
ircd::m::rooms::count_public(const string_view &server)
ircd::m::rooms::count(const opts &opts)
{
size_t ret{0};
const auto count{[&ret]
(const m::room::id &room_id)
for_each(opts, [&ret]
(const m::room::id &)
{
++ret;
return true;
}};
});
for_each_public(server, count);
return ret;
}
bool
ircd::m::rooms::for_each_public(const string_view &key,
const room::id::closure_bool &closure)
{
const room::state state
{
public_room_id
};
const bool is_room_id
{
m::valid(m::id::ROOM, key)
};
char state_key_buf[256];
const auto state_key
{
is_room_id?
make_state_key(state_key_buf, key):
key
};
const string_view &server
{
is_room_id?
room::id(key).host():
key
};
const room::state::keys_bool keys{[&closure, &server]
(const string_view &state_key) -> bool
{
const auto room_id
{
unmake_state_key(state_key)
};
if(server && room_id.host() != server)
return false;
return closure(room_id);
}};
return state.for_each("ircd.rooms", state_key, keys);
}
void
IRCD_MODULE_EXPORT
ircd::m::rooms::summary_chunk(const m::room &room,
json::stack::object &obj)
ircd::m::rooms::for_each(const room::id::closure_bool &closure)
{
return exists(room)?
local_summary_chunk(room, obj):
remote_summary_chunk(room, obj);
return for_each(opts_default, closure);
}
void
ircd::m::rooms::remote_summary_chunk(const m::room &room,
json::stack::object &obj)
bool
IRCD_MODULE_EXPORT
ircd::m::rooms::for_each(const opts &opts,
const room::id::closure_bool &closure)
{
const m::room publix
bool ret{true};
const auto proffer{[&opts, &closure, &ret]
(const m::room::id &room_id)
{
public_room_id
};
char state_key_buf[256];
const auto state_key
{
make_state_key(state_key_buf, room.room_id)
};
publix.get("ircd.rooms", state_key, [&obj]
(const m::event &event)
{
const json::object &summary
if(opts.room_id && !opts.lower_bound)
{
json::at<"content"_>(event)
};
ret = false;
return;
}
for(const auto &member : summary)
json::stack::member m{obj, member.first, member.second};
});
}
if(opts.room_id)
if(room_id < opts.room_id)
return;
void
ircd::m::rooms::local_summary_chunk(const m::room &room,
json::stack::object &obj)
{
static const event::keys::include keys
{
"content",
};
if(opts.server && !opts.summary)
if(opts.server != room_id.host())
return;
const m::event::fetch::opts fopts
{
keys, room.fopts? room.fopts->gopts : db::gopts{}
};
if(opts.summary)
if(!summary::has(room_id))
return;
const m::room::state state
{
room, &fopts
};
if(opts.server && opts.summary)
if(!room::aliases(room_id).count(opts.server))
return;
// Closure over boilerplate primary room state queries; i.e matrix
// m.room.$type events with state key "" where the content is directly
// presented to the closure.
const auto query{[&state]
(const string_view &type, const string_view &content_key, const auto &closure)
{
state.get(std::nothrow, type, "", [&content_key, &closure]
(const m::event &event)
{
const auto &content(json::get<"content"_>(event));
const auto &value(unquote(content.get(content_key)));
closure(value);
});
if(opts.join_rule)
if(!room(room_id).join_rule(opts.join_rule))
return;
ret = closure(room_id);
}};
// Aliases array
// branch for optimized public rooms searches.
if(opts.summary)
{
json::stack::member aliases_m{obj, "aliases"};
json::stack::array array{aliases_m};
state.for_each("m.room.aliases", [&array]
(const m::event &event)
const room::id::buf public_room_id
{
const json::array aliases
{
json::get<"content"_>(event).get("aliases")
};
"!public", my_host()
};
for(const string_view &alias : aliases)
array.append(unquote(alias));
const room::state state{public_room_id};
return state.for_each("ircd.rooms", opts.server, [&proffer, &ret]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
room::id::buf buf;
proffer(room::id::unswap(state_key, buf));
return ret;
});
}
query("m.room.avatar_url", "url", [&obj]
(const string_view &value)
return events::for_each_in_type("m.room.create", [&proffer, &ret]
(const string_view &type, const event::idx &event_idx)
{
json::stack::member
{
obj, "avatar_url", value
};
});
query("m.room.canonical_alias", "alias", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "canonical_alias", value
};
});
query("m.room.guest_access", "guest_access", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "guest_can_join", json::value
{
value == "can_join"
}
};
});
query("m.room.name", "name", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "name", value
};
});
// num_join_members
{
const m::room::members members{room};
json::stack::member
{
obj, "num_joined_members", json::value
{
long(members.count("join"))
}
};
}
// room_id
{
json::stack::member
{
obj, "room_id", room.room_id
};
}
query("m.room.topic", "topic", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "topic", value
};
});
query("m.room.history_visibility", "history_visibility", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "world_readable", json::value
{
value == "world_readable"
}
};
assert(type == "m.room.create");
m::get(std::nothrow, event_idx, "room_id", proffer);
return ret;
});
}
decltype(ircd::m::rooms::fetch_timeout)
ircd::m::rooms::fetch_timeout
{
{ "name", "ircd.m.rooms.fetch.timeout" },
{ "default", 45L /* matrix.org :-/ */ },
};
decltype(ircd::m::rooms::fetch_limit)
ircd::m::rooms::fetch_limit
{
{ "name", "ircd.m.rooms.fetch.limit" },
{ "default", 64L },
};
std::pair<size_t, std::string>
IRCD_MODULE_EXPORT
ircd::m::rooms::fetch_update(const net::hostport &hp,
const string_view &since,
const size_t &limit)
{
m::v1::public_rooms::opts opts;
opts.limit = limit;
opts.since = since;
opts.include_all_networks = true;
opts.dynamic = true;
// Buffer for headers and send content only; received content is dynamic
const unique_buffer<mutable_buffer> buf
{
16_KiB
};
m::v1::public_rooms request
{
hp, buf, std::move(opts)
};
request.wait(seconds(fetch_timeout));
const auto code
{
request.get()
};
const json::object response
{
request
};
const json::array &chunk
{
response.get("chunk")
};
for(const json::object &summary : chunk)
{
const m::room::id &room_id
{
unquote(summary.at("room_id"))
};
summary_set(room_id, summary);
}
return
{
response.get("total_room_count_estimate", 0UL),
unquote(response.get("next_batch", string_view{}))
};
}
ircd::m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::rooms::summary_del(const m::room &room)
{
char state_key_buf[m::event::STATE_KEY_MAX_SIZE];
const m::room::state state{public_room_id};
const m::event::idx &event_idx
{
state.get(std::nothrow, "ircd.rooms", make_state_key(state_key_buf, room.room_id))
};
if(!event_idx)
return {};
const m::event::id::buf event_id
{
m::event_id(event_idx)
};
return redact(public_room_id, m::me, event_id, "delisted");
}
ircd::m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::rooms::summary_set(const m::room::id &room_id,
const json::object &summary)
{
char state_key_buf[256];
const auto state_key
{
make_state_key(state_key_buf, room_id)
};
return send(public_room_id, m::me, "ircd.rooms", state_key, summary);
}
ircd::m::room::id::buf
ircd::m::rooms::unmake_state_key(const string_view &key)
{
const auto s
{
split(key, '!')
};
return m::room::id::buf
{
s.second, s.first
};
}
ircd::string_view
ircd::m::rooms::make_state_key(const mutable_buffer &buf,
const m::room::id &room_id)
{
mutable_buffer out{buf};
consume(out, copy(out, room_id.host()));
consume(out, copy(out, room_id.local()));
return string_view
{
data(buf), data(out)
};
}

421
modules/m_rooms_summary.cc Normal file
View file

@ -0,0 +1,421 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// 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::rooms::summary
{
static string_view make_state_key(const mutable_buffer &out, const m::room::id &);
static room::id::buf unmake_state_key(const string_view &);
static void chunk_remote(const room &, json::stack::object &o);
static void chunk_local(const room &, json::stack::object &o);
extern const room::id::buf public_room_id;
extern hookfn<vm::eval &> create_public_room;
}
ircd::mapi::header
IRCD_MODULE
{
"Matrix rooms summary"
};
decltype(ircd::m::rooms::summary::public_room_id)
ircd::m::rooms::summary::public_room_id
{
"public", ircd::my_host()
};
/// Create the public rooms room during initial database bootstrap.
/// This hooks the creation of the !ircd room which is a fundamental
/// event indicating the database has just been created.
decltype(ircd::m::rooms::summary::create_public_room)
ircd::m::rooms::summary::create_public_room
{
{
{ "_site", "vm.effect" },
{ "room_id", "!ircd" },
{ "type", "m.room.create" },
},
[](const m::event &, m::vm::eval &)
{
m::create(public_room_id, m::me.user_id);
}
};
//
// rooms::summary::fetch
//
decltype(ircd::m::rooms::summary::fetch::timeout)
ircd::m::rooms::summary::fetch::timeout
{
{ "name", "ircd.m.rooms.fetch.timeout" },
{ "default", 45L /* matrix.org :-/ */ },
};
decltype(ircd::m::rooms::summary::fetch::limit)
ircd::m::rooms::summary::fetch::limit
{
{ "name", "ircd.m.rooms.fetch.limit" },
{ "default", 64L },
};
//
// fetch::fetch
//
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::fetch::fetch(const net::hostport &hp,
const string_view &since,
const size_t &limit)
{
m::v1::public_rooms::opts opts;
opts.limit = limit;
opts.since = since;
opts.include_all_networks = true;
opts.dynamic = true;
const unique_buffer<mutable_buffer> buf
{
// Buffer for headers and send content; received content is dynamic
16_KiB
};
m::v1::public_rooms request
{
hp, buf, std::move(opts)
};
const auto code
{
request.get(seconds(timeout))
};
const json::object response
{
request
};
const json::array &chunk
{
response.get("chunk")
};
for(const json::object &summary : chunk)
{
const json::string &room_id
{
summary.at("room_id")
};
summary::set(room_id, summary);
}
this->total_room_count_estimate =
{
response.get("total_room_count_estimate", 0UL)
};
this->next_batch = json::string
{
response.get("next_batch", string_view{})
};
}
//
// rooms::summary
//
ircd::m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::del(const m::room &room)
{
const m::room::state state
{
public_room_id
};
char state_key_buf[m::event::STATE_KEY_MAX_SIZE];
const auto state_key
{
make_state_key(state_key_buf, room.room_id)
};
const m::event::idx &event_idx
{
state.get(std::nothrow, "ircd.rooms", state_key)
};
if(!event_idx)
return {};
const m::event::id::buf event_id
{
m::event_id(event_idx)
};
return redact(public_room_id, m::me, event_id, "delisted");
}
ircd::m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::set(const m::room &room)
{
if(!exists(room))
throw m::NOT_FOUND
{
"Cannot set a summary for room '%s' which I have no state for",
string_view{room.room_id}
};
const unique_buffer<mutable_buffer> buf
{
48_KiB
};
const json::object summary
{
chunk(room, buf)
};
return set(room.room_id, summary);
}
ircd::m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::set(const m::room::id &room_id,
const json::object &summary)
{
char state_key_buf[event::STATE_KEY_MAX_SIZE];
const auto state_key
{
make_state_key(state_key_buf, room_id)
};
return send(public_room_id, m::me, "ircd.rooms", state_key, summary);
}
ircd::json::object
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::chunk(const m::room &room,
const mutable_buffer &buf)
{
json::stack out{buf};
{
json::stack::object obj{out};
chunk(room, obj);
}
return json::object
{
out.completed()
};
}
void
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::chunk(const m::room &room,
json::stack::object &obj)
{
return exists(room)?
chunk_local(room, obj):
chunk_remote(room, obj);
}
void
ircd::m::rooms::summary::chunk_remote(const m::room &room,
json::stack::object &obj)
{
const m::room publix
{
public_room_id
};
char state_key_buf[event::STATE_KEY_MAX_SIZE];
const auto state_key
{
make_state_key(state_key_buf, room.room_id)
};
publix.get("ircd.rooms", state_key, [&obj]
(const m::event &event)
{
const json::object &summary
{
json::at<"content"_>(event)
};
for(const auto &member : summary)
json::stack::member m
{
obj, member.first, member.second
};
});
}
void
ircd::m::rooms::summary::chunk_local(const m::room &room,
json::stack::object &obj)
{
const m::room::state state
{
room
};
// Closure over boilerplate primary room state queries; i.e matrix
// m.room.$type events with state key "" where the content is directly
// presented to the closure.
const auto query{[&state]
(const auto &type, const auto &content_key, const auto &closure)
{
const auto event_idx
{
state.get(std::nothrow, type, "")
};
m::get(std::nothrow, event_idx, "content", [&content_key, &closure]
(const json::object &content)
{
const json::string &value
{
content.get(content_key)
};
closure(value);
});
}};
// Aliases array
{
json::stack::member aliases_m{obj, "aliases"};
json::stack::array array{aliases_m};
state.for_each("m.room.aliases", [&array]
(const m::event &event)
{
const json::array aliases
{
json::get<"content"_>(event).get("aliases")
};
for(const string_view &alias : aliases)
array.append(unquote(alias));
});
}
query("m.room.avatar_url", "url", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "avatar_url", value
};
});
query("m.room.canonical_alias", "alias", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "canonical_alias", value
};
});
query("m.room.guest_access", "guest_access", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "guest_can_join", json::value
{
value == "can_join"
}
};
});
query("m.room.name", "name", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "name", value
};
});
// num_join_members
{
const m::room::members members{room};
json::stack::member
{
obj, "num_joined_members", json::value
{
long(members.count("join"))
}
};
}
// room_id
{
json::stack::member
{
obj, "room_id", room.room_id
};
}
query("m.room.topic", "topic", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "topic", value
};
});
query("m.room.history_visibility", "history_visibility", [&obj]
(const string_view &value)
{
json::stack::member
{
obj, "world_readable", json::value
{
value == "world_readable"
}
};
});
}
bool
IRCD_MODULE_EXPORT
ircd::m::rooms::summary::has(const room::id &room_id)
{
const m::room::state state
{
public_room_id
};
char state_key_buf[m::event::STATE_KEY_MAX_SIZE];
const auto state_key
{
make_state_key(state_key_buf, room_id)
};
return state.has("ircd.rooms", state_key);
}
ircd::m::room::id::buf
ircd::m::rooms::summary::unmake_state_key(const string_view &key)
{
m::room::id::buf ret;
return m::room::id::unswap(key, ret);
}
ircd::string_view
ircd::m::rooms::summary::make_state_key(const mutable_buffer &buf,
const m::room::id &room_id)
{
return room_id.swap(buf);
}