0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-15 14:31:11 +01:00
construct/ircd/m/room.cc

2479 lines
45 KiB
C++

// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 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.
int64_t
ircd::m::depth(const id::room &room_id)
{
return std::get<int64_t>(top(room_id));
}
int64_t
ircd::m::depth(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return -1;
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<0>(part);
}
ircd::m::event::idx
ircd::m::head_idx(const id::room &room_id)
{
return std::get<event::idx>(top(room_id));
}
ircd::m::event::idx
ircd::m::head_idx(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return 0;
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<1>(part);
}
ircd::m::event::id::buf
ircd::m::head(const id::room &room_id)
{
return std::get<event::id::buf>(top(room_id));
}
ircd::m::event::id::buf
ircd::m::head(std::nothrow_t,
const id::room &room_id)
{
return std::get<event::id::buf>(top(std::nothrow, room_id));
}
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(const id::room &room_id)
{
const auto ret
{
top(std::nothrow, room_id)
};
if(std::get<int64_t>(ret) == -1)
throw m::NOT_FOUND
{
"No head for room %s", string_view{room_id}
};
return ret;
}
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return
{
event::id::buf{}, -1, 0
};
const auto part
{
dbs::room_events_key(it->first)
};
const int64_t &depth
{
int64_t(std::get<0>(part))
};
const event::idx &event_idx
{
std::get<1>(part)
};
std::tuple<event::id::buf, int64_t, event::idx> ret
{
event::id::buf{}, depth, event_idx
};
event::fetch::event_id(event_idx, std::nothrow, [&ret]
(const event::id &event_id)
{
std::get<event::id::buf>(ret) = event_id;
});
return ret;
}
uint
ircd::m::version(const id::room &room_id)
{
static const m::event::fetch::opts fopts
{
event::keys::include
{
"content",
}
};
const m::room::state state
{
room_id, &fopts
};
uint ret;
state.get("m.room.create", "", [&ret]
(const m::event &event)
{
const auto version_string
{
unquote(json::get<"content"_>(event).get("version", "1"))
};
ret = version_string?
lex_cast<uint>(version_string):
1;
});
return ret;
}
bool
ircd::m::creator(const id::room &room_id,
const id::user &user_id)
{
const auto creator_user_id
{
creator(room_id)
};
return creator_user_id == user_id;
}
ircd::m::id::user::buf
ircd::m::creator(const id::room &room_id)
{
// Query the sender field of the event to get the creator. This is for
// future compatibility if the content.creator field gets eliminated.
static const event::fetch::opts fopts
{
event::keys::include
{
"sender",
}
};
const room::state state
{
room_id, &fopts
};
id::user::buf ret;
state.get("m.room.create", "", [&ret]
(const m::event &event)
{
ret = user::id
{
json::get<"sender"_>(event)
};
});
return ret;
}
bool
ircd::m::federate(const id::room &room_id)
{
static const m::event::fetch::opts fopts
{
event::keys::include
{
"content",
}
};
const m::room::state state
{
room_id, &fopts
};
bool ret;
state.get("m.room.create", "", [&ret]
(const m::event &event)
{
ret = json::get<"content"_>(event).get("m.federate", true);
});
return ret;
}
bool
ircd::m::exists(const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
return bool(it);
}
bool
ircd::m::exists(const room &room)
{
return exists(room.room_id);
}
bool
ircd::m::my(const room &room)
{
return my(room.room_id);
}
//
// room
//
/// Test of the join_rule of the room is the argument.
bool
ircd::m::room::join_rule(const string_view &rule)
const
{
char buf[32];
return join_rule(mutable_buffer{buf}) == rule;
}
/// Receive the join_rule of the room into buffer of sufficient size.
/// The protocol does not specify a join_rule string longer than 7
/// characters but do be considerate of the future. This function
/// properly defaults the string as per the protocol spec.
ircd::string_view
ircd::m::room::join_rule(const mutable_buffer &out)
const
{
static const string_view default_join_rule
{
"invite"
};
string_view ret
{
default_join_rule
};
const event::keys::include keys
{
"content"
};
const m::event::fetch::opts fopts
{
keys, this->fopts? this->fopts->gopts : db::gopts{}
};
const room::state state
{
*this, &fopts
};
state.get(std::nothrow, "m.room.join_rules", "", [&ret, &out]
(const m::event &event)
{
const auto &content
{
json::get<"content"_>(event)
};
const string_view &rule
{
content.get("join_rule", default_join_rule)
};
ret = string_view
{
data(out), copy(out, unquote(rule))
};
});
return ret;
}
/// The only joined members are from our origin (local only). This indicates
/// we won't have any other federation servers to query for room data, nor do
/// we need to broadcast events to the federation. This is not an authority
/// about a room's type or ability to federate. Returned value changes to false
/// when another origin joins.
bool
ircd::m::room::lonly()
const
{
const origins origins(*this);
return origins.only(my_host());
}
bool
ircd::m::room::visible(const string_view &mxid,
const event *const &event)
const
{
if(event)
return m::visible(*event, mxid);
const m::event event_
{
{ "event_id", event_id },
{ "room_id", room_id },
};
return m::visible(event_, mxid);
}
bool
ircd::m::room::membership(const m::id::user &user_id,
const string_view &membership)
const
{
char buf[64];
return this->membership(buf, user_id) == membership;
}
ircd::string_view
ircd::m::room::membership(const mutable_buffer &out,
const m::id::user &user_id)
const
{
static const event::keys membership_keys
{
event::keys::include{"membership"}
};
// Since this is a member function of m::room there might be a supplied
// fopts. Whatever keys it has are irrelevant, but we can preserve gopts.
const m::event::fetch::opts fopts
{
membership_keys, this->fopts? this->fopts->gopts : db::gopts{}
};
string_view ret;
const auto result_closure{[&out, &ret]
(const m::event &event)
{
ret =
{
data(out), copy(out, m::membership(event))
};
}};
const room::state state{*this, &fopts};
const bool exists
{
state.get(std::nothrow, "m.room.member"_sv, user_id, result_closure)
};
// This branch is taken when the event exists but the event.membership had
// no value. Due to wanton protocol violations event.membership may be
// jsundefined and only event.content.membership will exist in event. This
// is a rare case so both queries are optimized to only seek for their key.
if(exists && !ret)
{
static const event::keys content_membership_keys
{
event::keys::include{"content"}
};
const m::event::fetch::opts fopts
{
content_membership_keys, this->fopts? this->fopts->gopts : db::gopts{}
};
const room::state state{*this, &fopts};
state.get(std::nothrow, "m.room.member"_sv, user_id, result_closure);
}
return ret;
}
bool
ircd::m::room::has(const string_view &type)
const
{
return get(std::nothrow, type, nullptr);
}
void
ircd::m::room::get(const string_view &type,
const event::closure &closure)
const
{
if(!get(std::nothrow, type, closure))
throw m::NOT_FOUND
{
"No events of type '%s' found in '%s'",
type,
room_id
};
}
bool
ircd::m::room::get(std::nothrow_t,
const string_view &type,
const event::closure &closure)
const
{
bool ret{false};
for_each(type, event::closure_bool{[&ret, &closure]
(const event &event)
{
if(closure)
closure(event);
ret = true;
return false;
}});
return ret;
}
void
ircd::m::room::get(const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
const state state{*this};
state.get(type, state_key, closure);
}
bool
ircd::m::room::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
const state state{*this};
return state.get(std::nothrow, type, state_key, closure);
}
bool
ircd::m::room::has(const string_view &type,
const string_view &state_key)
const
{
const state state{*this};
return state.has(type, state_key);
}
void
ircd::m::room::for_each(const event::closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const event::id::closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::id::closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const event::closure_idx &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::closure_idx_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const string_view &type,
const event::closure &closure)
const
{
for_each(type, event::closure_bool{[&closure]
(const event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(type, event::closure_idx_bool{[&closure, &event]
(const event::idx &event_idx)
{
if(!seek(event, event_idx, std::nothrow))
return true;
return closure(event);
}});
}
void
ircd::m::room::for_each(const string_view &type,
const event::id::closure &closure)
const
{
for_each(type, event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::id::closure_bool &closure)
const
{
return for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
event::fetch::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &event_id)
{
ret = closure(event_id);
});
return ret;
}});
}
void
ircd::m::room::for_each(const string_view &type,
const event::closure_idx &closure)
const
{
for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
closure(idx);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::closure_idx_bool &closure)
const
{
static constexpr auto idx
{
json::indexof<event, "type"_>()
};
auto &column
{
dbs::event_column.at(idx)
};
messages it{*this};
for(; it; --it)
{
const auto &event_idx
{
it.event_idx()
};
bool match
{
empty(type) // allow empty type to always match and bypass query
};
if(!match)
column(byte_view<string_view>(event_idx), std::nothrow, [&match, &type]
(const string_view &value)
{
match = value == type;
});
if(match)
if(!closure(event_idx))
return false;
}
return true;
}
//
// room::messages
//
ircd::m::room::messages::messages(const m::room &room,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
if(room.event_id)
seek(room.event_id);
else
seek();
}
ircd::m::room::messages::messages(const m::room &room,
const event::id &event_id,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
seek(event_id);
}
ircd::m::room::messages::messages(const m::room &room,
const uint64_t &depth,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
seek(depth);
}
const ircd::m::event &
ircd::m::room::messages::operator*()
{
return fetch(std::nothrow);
};
bool
ircd::m::room::messages::seek()
{
this->it = dbs::room_events.begin(room.room_id);
return bool(*this);
}
bool
ircd::m::room::messages::seek(const event::id &event_id)
try
{
const event::idx &event_idx
{
index(event_id)
};
return seek_idx(event_idx);
}
catch(const db::not_found &e)
{
return false;
}
bool
ircd::m::room::messages::seek(const uint64_t &depth)
{
char buf[dbs::ROOM_EVENTS_KEY_MAX_SIZE];
const auto seek_key
{
dbs::room_events_key(buf, room.room_id, depth)
};
this->it = dbs::room_events.begin(seek_key);
return bool(*this);
}
bool
ircd::m::room::messages::seek_idx(const event::idx &event_idx)
try
{
uint64_t depth(0);
if(event_idx)
m::get(event_idx, "depth", mutable_buffer
{
reinterpret_cast<char *>(&depth), sizeof(depth)
});
char buf[dbs::ROOM_EVENTS_KEY_MAX_SIZE];
const auto &seek_key
{
dbs::room_events_key(buf, room.room_id, depth, event_idx)
};
this->it = dbs::room_events.begin(seek_key);
if(!bool(*this))
return false;
// Check if this event_idx is actually in this room
if(event_idx && event_idx != this->event_idx())
return false;
return true;
}
catch(const db::not_found &e)
{
return false;
}
ircd::m::event::id::buf
ircd::m::room::messages::event_id()
const
{
event::id::buf ret;
event::fetch::event_id(this->event_idx(), [&ret]
(const event::id &event_id)
{
ret = event_id;
});
return ret;
}
ircd::string_view
ircd::m::room::messages::state_root()
const
{
assert(bool(*this));
return it->second;
}
uint64_t
ircd::m::room::messages::depth()
const
{
assert(bool(*this));
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<0>(part);
}
ircd::m::event::idx
ircd::m::room::messages::event_idx()
const
{
assert(bool(*this));
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<1>(part);
}
const ircd::m::event &
ircd::m::room::messages::fetch()
{
m::seek(_event, event_idx());
return _event;
}
const ircd::m::event &
ircd::m::room::messages::fetch(std::nothrow_t)
{
const event::fetch::opts &fopts
{
room.fopts?
*room.fopts:
event::fetch::default_opts
};
if(!m::seek(_event, event_idx(), std::nothrow))
_event = m::event::fetch
{
fopts
};
return _event;
}
//
// room::state
//
decltype(ircd::m::room::state::disable_history)
ircd::m::room::state::disable_history
{
{ "name", "ircd.m.room.state.disable_history" },
{ "default", false },
};
decltype(ircd::m::room::state::readahead_size)
ircd::m::room::state::readahead_size
{
{ "name", "ircd.m.room.state.readahead_size" },
{ "default", 0L },
};
//
// room::state::state
//
ircd::m::room::state::state(const m::room &room,
const event::fetch::opts *const &fopts)
:room_id
{
room.room_id
}
,event_id
{
room.event_id?
event::id::buf{room.event_id}:
event::id::buf{}
}
,root_id
{
event_id && !bool(disable_history)?
dbs::state_root(root_id_buf, room_id, event_id):
m::state::id{}
}
,fopts
{
fopts?
fopts:
room.fopts
}
{
}
ircd::m::event::id::buf
ircd::m::room::state::get(const string_view &type,
const string_view &state_key)
const
{
event::id::buf ret;
get(type, state_key, event::id::closure{[&ret]
(const event::id &event_id)
{
ret = event_id;
}});
return ret;
}
ircd::m::event::id::buf
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key)
const
{
event::id::buf ret;
get(std::nothrow, type, state_key, event::id::closure{[&ret]
(const event::id &event_id)
{
ret = event_id;
}});
return ret;
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
get(type, state_key, event::closure_idx{[this, &closure]
(const event::idx &event_idx)
{
const event::fetch event
{
event_idx, fopts? *fopts : event::fetch::default_opts
};
closure(event);
}});
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::id::closure &closure)
const try
{
if(!present())
return m::state::get(root_id, type, state_key, [&closure]
(const string_view &event_id)
{
closure(unquote(event_id));
});
get(type, state_key, event::closure_idx{[&closure]
(const event::idx &idx)
{
event::fetch::event_id(idx, closure);
}});
}
catch(const db::not_found &e)
{
throw m::NOT_FOUND
{
"(%s,%s) in %s :%s",
type,
state_key,
string_view{room_id},
e.what()
};
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::closure_idx &closure)
const try
{
if(!present())
m::state::get(root_id, type, state_key, [&closure]
(const string_view &event_id)
{
closure(index(unquote(event_id)));
});
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
column(dbs::room_state_key(key, room_id, type, state_key), [&closure]
(const string_view &value)
{
closure(byte_view<event::idx>(value));
});
}
catch(const db::not_found &e)
{
throw m::NOT_FOUND
{
"(%s,%s) in %s :%s",
type,
state_key,
string_view{room_id},
e.what()
};
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
return get(std::nothrow, type, state_key, event::closure_idx{[this, &closure]
(const event::idx &event_idx)
{
const event::fetch event
{
event_idx, std::nothrow, fopts? *fopts : event::fetch::default_opts
};
closure(event);
}});
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::id::closure &closure)
const
{
if(!present())
return m::state::get(std::nothrow, root_id, type, state_key, [&closure]
(const string_view &event_id)
{
closure(unquote(event_id));
});
return get(std::nothrow, type, state_key, event::closure_idx{[&closure]
(const event::idx &idx)
{
event::fetch::event_id(idx, std::nothrow, closure);
}});
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure_idx &closure)
const
{
if(!present())
return m::state::get(std::nothrow, root_id, type, state_key, [&closure]
(const string_view &event_id)
{
return closure(index(unquote(event_id), std::nothrow));
});
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
return column(dbs::room_state_key(key, room_id, type, state_key), std::nothrow, [&closure]
(const string_view &value)
{
closure(byte_view<event::idx>(value));
});
}
bool
ircd::m::room::state::has(const string_view &type)
const
{
return for_each(type, event::id::closure_bool{[](const m::event::id &)
{
return true;
}});
}
bool
ircd::m::room::state::has(const string_view &type,
const string_view &state_key)
const
{
if(!present())
return m::state::get(std::nothrow, root_id, type, state_key, []
(const string_view &event_id)
{});
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
return db::has(column, dbs::room_state_key(key, room_id, type, state_key));
}
size_t
ircd::m::room::state::count()
const
{
return count(string_view{});
}
size_t
ircd::m::room::state::count(const string_view &type)
const
{
if(!present())
return m::state::count(root_id, type);
size_t ret{0};
for_each(type, event::closure_idx{[&ret]
(const event::idx &event_idx)
{
++ret;
}});
return ret;
}
void
ircd::m::room::state::for_each(const event::closure &closure)
const
{
for_each(event::closure_bool{[&closure]
(const m::event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(event::closure_idx_bool{[&event, &closure]
(const event::idx &event_idx)
{
if(seek(event, event_idx, std::nothrow))
if(!closure(event))
return false;
return true;
}});
}
void
ircd::m::room::state::for_each(const event::id::closure &closure)
const
{
for_each(event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::id::closure_bool &closure)
const
{
if(!present())
return m::state::for_each(root_id, m::state::iter_bool_closure{[&closure]
(const json::array &key, const string_view &event_id)
{
return closure(unquote(event_id));
}});
return for_each(event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
event::fetch::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &id)
{
ret = closure(id);
});
return ret;
}});
}
void
ircd::m::room::state::for_each(const event::closure_idx &closure)
const
{
for_each(event::closure_idx_bool{[&closure]
(const event::idx &event_idx)
{
closure(event_idx);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::closure_idx_bool &closure)
const
{
if(!present())
return m::state::for_each(root_id, m::state::iter_bool_closure{[&closure]
(const json::array &key, const string_view &event_id)
{
return closure(index(unquote(event_id), std::nothrow));
}});
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
if(!opts.readahead)
opts.readahead = size_t(readahead_size);
auto &column{dbs::room_state};
for(auto it{column.begin(room_id, opts)}; bool(it); ++it)
if(!closure(byte_view<event::idx>(it->second)))
return false;
return true;
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::closure &closure)
const
{
for_each(type, event::closure_bool{[&closure]
(const m::event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::closure_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::id::closure &closure)
const
{
for_each(type, event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::id::closure_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::closure_idx &closure)
const
{
for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &event_idx)
{
closure(event_idx);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::closure_idx_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(type, state_key_lb, event::closure_idx_bool{[&event, &closure]
(const event::idx &event_idx)
{
if(seek(event, event_idx, std::nothrow))
if(!closure(event))
return false;
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::id::closure_bool &closure)
const
{
if(!present())
return m::state::for_each(root_id, type, state_key_lb, [&closure]
(const json::array &key, const string_view &event_id)
{
return closure(unquote(event_id));
});
return for_each(type, state_key_lb, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
event::fetch::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &id)
{
ret = closure(id);
});
return ret;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::closure_idx_bool &closure)
const
{
if(!present())
return m::state::for_each(root_id, type, state_key_lb, [&closure]
(const json::array &key, const string_view &event_id)
{
return closure(index(unquote(event_id), std::nothrow));
});
char keybuf[dbs::ROOM_STATE_KEY_MAX_SIZE];
const auto &key
{
dbs::room_state_key(keybuf, room_id, type, state_key_lb)
};
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
if(!opts.readahead)
opts.readahead = size_t(readahead_size);
auto &column{dbs::room_state};
for(auto it{column.begin(key, opts)}; bool(it); ++it)
if(std::get<0>(dbs::room_state_key(it->first)) == type)
{
if(!closure(byte_view<event::idx>(it->second)))
return false;
}
else break;
return true;
}
void
ircd::m::room::state::for_each(const string_view &type,
const keys &closure)
const
{
for_each(type, keys_bool{[&closure]
(const string_view &key)
{
closure(key);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const keys_bool &closure)
const
{
return for_each(type, string_view{}, closure);
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const keys_bool &closure)
const
{
if(!present())
return m::state::for_each(root_id, type, state_key_lb, [&closure]
(const json::array &key, const string_view &event_id)
{
assert(size(key) >= 2);
return closure(unquote(key.at(1)));
});
char keybuf[dbs::ROOM_STATE_KEY_MAX_SIZE];
const auto &key
{
dbs::room_state_key(keybuf, room_id, type, state_key_lb)
};
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
if(!opts.readahead)
opts.readahead = size_t(readahead_size);
auto &column{dbs::room_state};
for(auto it{column.begin(key, opts)}; bool(it); ++it)
{
const auto key
{
dbs::room_state_key(it->first)
};
if(std::get<0>(key) == type)
{
if(!closure(std::get<1>(key)))
return false;
}
else break;
}
return true;
}
void
ircd::m::room::state::for_each(const types &closure)
const
{
for_each(types_bool{[&closure]
(const string_view &type)
{
closure(type);
return true;
}});
}
bool
ircd::m::room::state::for_each(const types_bool &closure)
const
{
string_view last;
char lastbuf[256]; //TODO: type maxlen
if(!present())
{
m::state::for_each(root_id, m::state::iter_bool_closure{[&closure, &last, &lastbuf]
(const json::array &key, const string_view &)
{
assert(size(key) >= 2);
const auto type
{
unquote(key.at(0))
};
if(type == last)
return true;
last = strlcpy(lastbuf, type);
return closure(type);
}});
return true;
}
char keybuf[dbs::ROOM_STATE_KEY_MAX_SIZE];
const auto &key
{
dbs::room_state_key(keybuf, room_id, string_view{})
};
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
auto &column{dbs::room_state};
for(auto it{column.begin(key, opts)}; bool(it); ++it)
{
const auto part
{
dbs::room_state_key(it->first)
};
const auto &type
{
std::get<0>(part)
};
if(type == last)
continue;
last = strlcpy(lastbuf, type);
if(!closure(type))
return false;
}
return true;
}
/// Figure out if this instance of room::state is presenting the current
/// "present" state of the room or the state of the room at some previous
/// event. This is an important distinction because the present state of
/// the room should provide optimal performance for the functions of this
/// interface by using the present state table. Prior states will use the
/// state btree.
bool
ircd::m::room::state::present()
const
{
// When no event_id is passed to the state constructor that immediately
// indicates the present state of the room is sought.
if(!event_id)
return true;
// When an event_id is passed but no state btree root_id was found
// that means there is no state btree generated for this room or at
// this event_id. Right now we fall back to using the present state
// of the room, which may be confusing behavior. In production this
// shouldn't happen; if it does, the ctor should probably throw rather
// than this. We just fallback to considering present state here.
if(!root_id)
return true;
// Check the cached value from a previous false result of this function
// before doing any real work/IO below. If this function ever returned
// false it will never return true after.
if(_not_present)
return false;
const auto head_id
{
m::head(std::nothrow, room_id)
};
// If the event_id passed is exactly the latest event we can obviously
// consider this the present state.
if(!head_id || head_id == event_id)
return true;
m::state::id_buffer root_id_buf;
const auto head_root
{
dbs::state_root(root_id_buf, room_id, head_id)
};
// If the room's state has not changed between the event_id desired
// here and the latest event we can also consider this the present state.
if(head_root && head_root == root_id)
return true;
// This result is cacheable because once it's no longer the present
// it will never be again. Panta chorei kai ouden menei. Note that this
// is a const member function; the cache variable is an appropriate case
// for the 'mutable' keyword.
_not_present = true;
return false;
}
//
// room::members
//
size_t
ircd::m::room::members::count()
const
{
const room::state state
{
room
};
return state.count("m.room.member");
}
size_t
ircd::m::room::members::count(const string_view &membership)
const
{
// Allow empty membership string to count all memberships
if(!membership)
return count();
// joined members optimization. Only possible when seeking
// membership="join" on the present state of the room.
if(!room.event_id && membership == "join")
{
size_t ret{0};
const room::origins origins{room};
origins._for_each_([&ret](const string_view &)
{
++ret;
return true;
});
return ret;
}
// The list of event fields to fetch for the closure
static const event::keys keys
{
event::keys::include
{
"event_id", // Added for any upstack usage (but may be unnecessary).
"membership", // Required for membership test.
"content", // Required because synapse events randomly have no event.membership
}
};
const m::event::fetch::opts fopts
{
keys, room.fopts? room.fopts->gopts : db::gopts{}
};
const room::state state
{
room, &fopts
};
size_t ret{0};
state.for_each("m.room.member", event::closure{[&ret, &membership]
(const m::event &event)
{
ret += m::membership(event) == membership;
}});
return ret;
}
void
ircd::m::room::members::for_each(const closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::members::for_each(const closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::members::for_each(const event::closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::members::for_each(const event::closure_bool &closure)
const
{
const room::state state{room};
return state.for_each("m.room.member", event::closure_bool{[&closure]
(const m::event &event)
{
return closure(event);
}});
}
void
ircd::m::room::members::for_each(const string_view &membership,
const closure &closure)
const
{
for_each(membership, closure_bool{[&closure]
(const user::id &user_id)
{
closure(user_id);
return true;
}});
}
/// Iterate the mxid's of the users in the room, optionally with a specific
/// membership state. This query contains internal optimizations as the closure
/// only requires a user::id. The db::gopts set in the room.fopts pointer is
/// still used if provided.
bool
ircd::m::room::members::for_each(const string_view &membership,
const closure_bool &closure)
const
{
// Setup the list of event fields to fetch for the closure
static const event::keys keys
{
event::keys::include
{
"event_id",
"membership",
"state_key",
"content", // Required because synapse events randomly have no event.membership
}
};
// In this case the fetch opts isn't static so it can maintain the
// previously given db::gopts, but it will use our keys list.
const m::event::fetch::opts fopts
{
keys, room.fopts? room.fopts->gopts : db::gopts{}
};
// Stack-over the the current fetch opts with our new opts for this query,
// putting them back when we're finished. This requires a const_cast which
// should be okay here.
auto &room(const_cast<m::room &>(this->room));
const auto theirs{room.fopts};
room.fopts = &fopts;
const unwind reset{[&room, &theirs]
{
room.fopts = theirs;
}};
return for_each(membership, event::closure_bool{[&closure]
(const event &event)
{
const user::id &user_id
{
at<"state_key"_>(event)
};
return closure(user_id);
}});
}
void
ircd::m::room::members::for_each(const string_view &membership,
const event::closure &closure)
const
{
for_each(membership, event::closure_bool{[&closure]
(const m::event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::members::for_each(const string_view &membership,
const event::closure_bool &closure)
const
{
if(empty(membership))
return for_each(closure);
// joined members optimization. Only possible when seeking
// membership="join" on the present state of the room.
if(!room.event_id && membership == "join")
{
const room::origins origins{room};
return origins._for_each_([&closure, this]
(const string_view &key)
{
const string_view &member
{
std::get<1>(dbs::room_joined_key(key))
};
bool ret{true};
room.get(std::nothrow, "m.room.member", member, event::closure{[&closure, &ret]
(const event &event)
{
ret = closure(event);
}});
return ret;
});
}
return for_each(event::closure_bool{[&membership, &closure]
(const event &event)
{
if(m::membership(event) == membership)
if(!closure(event))
return false;
return true;
}});
}
//
// room::origins
//
ircd::string_view
ircd::m::room::origins::random(const mutable_buffer &buf,
const closure_bool &proffer)
const
{
string_view ret;
const auto closure{[&buf, &proffer, &ret]
(const string_view &origin)
{
ret = { data(buf), copy(buf, origin) };
}};
random(closure, proffer);
return ret;
}
bool
ircd::m::room::origins::random(const closure &view,
const closure_bool &proffer)
const
{
using prototype = bool (const m::room &,
const m::room::origins::closure &,
const m::room::origins::closure_bool &);
static mods::import<prototype> random_origin
{
"m_room", "random_origin"
};
return random_origin(room, view, proffer);
}
size_t
ircd::m::room::origins::count()
const
{
size_t ret{0};
for_each([&ret](const string_view &)
{
++ret;
});
return ret;
}
/// Tests if argument is the only origin in the room.
/// If a zero or more than one origins exist, returns false. If the only origin
/// in the room is the argument origin, returns true.
bool
ircd::m::room::origins::only(const string_view &origin)
const
{
ushort ret{2};
for_each(closure_bool{[&ret, &origin]
(const string_view &origin_) -> bool
{
if(origin == origin_)
ret = 1;
else
ret = 0;
return ret;
}});
return ret == 1;
}
bool
ircd::m::room::origins::has(const string_view &origin)
const
{
db::index &index
{
dbs::room_joined
};
char querybuf[dbs::ROOM_JOINED_KEY_MAX_SIZE];
const auto query
{
dbs::room_joined_key(querybuf, room.room_id, origin)
};
auto it
{
index.begin(query)
};
if(!it)
return false;
const string_view &key
{
lstrip(it->first, "\0"_sv)
};
const string_view &key_origin
{
std::get<0>(dbs::room_joined_key(key))
};
return key_origin == origin;
}
void
ircd::m::room::origins::for_each(const closure &view)
const
{
for_each(closure_bool{[&view]
(const string_view &origin)
{
view(origin);
return true;
}});
}
bool
ircd::m::room::origins::for_each(const closure_bool &view)
const
{
string_view last;
char lastbuf[rfc1035::NAME_BUF_SIZE];
return _for_each_([&last, &lastbuf, &view]
(const string_view &key)
{
const string_view &origin
{
std::get<0>(dbs::room_joined_key(key))
};
if(origin == last)
return true;
if(!view(origin))
return false;
last = { lastbuf, copy(lastbuf, origin) };
return true;
});
}
bool
ircd::m::room::origins::_for_each_(const closure_bool &view)
const
{
db::index &index
{
dbs::room_joined
};
auto it
{
index.begin(room.room_id)
};
for(; bool(it); ++it)
{
const string_view &key
{
lstrip(it->first, "\0"_sv)
};
if(!view(key))
return false;
}
return true;
}
//
// room::head
//
size_t
ircd::m::room::head::count()
const
{
size_t ret(0);
for_each([&ret]
(const event::idx &event_idx, const event::id &event_id)
{
++ret;
});
return ret;
}
bool
ircd::m::room::head::has(const event::id &event_id)
const
{
bool ret{false};
for_each(closure_bool{[&ret, &event_id]
(const event::idx &event_idx, const event::id &event_id_)
{
ret = event_id_ == event_id;
return !ret; // for_each protocol: false to break
}});
return ret;
}
void
ircd::m::room::head::for_each(const closure &closure)
const
{
for_each(closure_bool{[&closure]
(const event::idx &event_idx, const event::id &event_id)
{
closure(event_idx, event_id);
return true;
}});
}
bool
ircd::m::room::head::for_each(const closure_bool &closure)
const
{
auto &index
{
dbs::room_head
};
auto it
{
index.begin(room.room_id)
};
for(; it; ++it)
{
const event::id &event_id
{
dbs::room_head_key(it->first)
};
const event::idx &event_idx
{
byte_view<event::idx>{it->second}
};
if(!closure(event_idx, event_id))
return false;
}
return true;
}
//
// room::power
//
decltype(ircd::m::room::power::default_creator_level)
ircd::m::room::power::default_creator_level
{
100
};
decltype(ircd::m::room::power::default_power_level)
ircd::m::room::power::default_power_level
{
50
};
decltype(ircd::m::room::power::default_event_level)
ircd::m::room::power::default_event_level
{
0
};
decltype(ircd::m::room::power::default_user_level)
ircd::m::room::power::default_user_level
{
0
};
bool
ircd::m::room::power::operator()(const m::user::id &user_id,
const string_view &prop,
const string_view &type,
const string_view &state_key)
const
{
const auto &user_level
{
level_user(user_id)
};
const auto &required_level
{
prop == "events"?
level_event(type, state_key):
level(prop)
};
return user_level >= required_level;
}
int64_t
ircd::m::room::power::level_user(const m::user::id &user_id)
const try
{
int64_t ret
{
default_user_level
};
const auto closure{[&user_id, &ret]
(const json::object &content)
{
const auto users_default
{
content.get<int64_t>("users_default", default_user_level)
};
const json::object &users
{
content.at("users")
};
ret = users.get<int64_t>(user_id, users_default);
}};
const bool has_power_levels_event
{
view(closure)
};
if(!has_power_levels_event)
if(creator(room, user_id))
ret = default_creator_level;
return ret;
}
catch(const json::error &e)
{
return default_user_level;
}
int64_t
ircd::m::room::power::level_event(const string_view &type)
const try
{
int64_t ret
{
default_event_level
};
view([&type, &ret]
(const json::object &content)
{
const auto &events_default
{
content.get<int64_t>("events_default", default_event_level)
};
const json::object &events
{
content.get("events")
};
ret = events.get<int64_t>(type, events_default);
});
return ret;
}
catch(const json::error &e)
{
return default_event_level;
}
int64_t
ircd::m::room::power::level_event(const string_view &type,
const string_view &state_key)
const try
{
if(!defined(state_key))
return level_event(type);
int64_t ret
{
default_power_level
};
view([&type, &ret]
(const json::object &content)
{
const auto &state_default
{
content.get<int64_t>("state_default", default_power_level)
};
const json::object &events
{
content.get("events")
};
ret = events.get<int64_t>(type, state_default);
});
return ret;
}
catch(const json::error &e)
{
return default_power_level;
}
int64_t
ircd::m::room::power::level(const string_view &prop)
const try
{
int64_t ret
{
default_power_level
};
view([&prop, &ret]
(const json::object &content)
{
ret = content.at<int64_t>(prop);
});
return ret;
}
catch(const json::error &e)
{
return default_power_level;
}
size_t
ircd::m::room::power::count_levels()
const
{
size_t ret{0};
for_each([&ret]
(const string_view &, const int64_t &)
{
++ret;
});
return ret;
}
size_t
ircd::m::room::power::count_collections()
const
{
size_t ret{0};
view([&ret]
(const json::object &content)
{
for(const auto &member : content)
ret += json::type(member.second) == json::OBJECT;
});
return ret;
}
size_t
ircd::m::room::power::count(const string_view &prop)
const
{
size_t ret{0};
for_each(prop, [&ret]
(const string_view &, const int64_t &)
{
++ret;
});
return ret;
}
bool
ircd::m::room::power::has_event(const string_view &type)
const try
{
bool ret{false};
view([&type, &ret]
(const json::object &content)
{
const json::object &events
{
content.at("events")
};
const string_view &level
{
events.at(type)
};
ret = json::type(level) == json::NUMBER;
});
return ret;
}
catch(const json::error &)
{
return false;
}
bool
ircd::m::room::power::has_user(const m::user::id &user_id)
const try
{
bool ret{false};
view([&user_id, &ret]
(const json::object &content)
{
const json::object &users
{
content.at("users")
};
const string_view &level
{
users.at(user_id)
};
ret = json::type(level) == json::NUMBER;
});
return ret;
}
catch(const json::error &)
{
return false;
}
bool
ircd::m::room::power::has_collection(const string_view &prop)
const
{
bool ret{false};
view([&prop, &ret]
(const json::object &content)
{
const auto &value{content.get(prop)};
if(value && json::type(value) == json::OBJECT)
ret = true;
});
return ret;
}
bool
ircd::m::room::power::has_level(const string_view &prop)
const
{
bool ret{false};
view([&prop, &ret]
(const json::object &content)
{
const auto &value{content.get(prop)};
if(value && json::type(value) == json::NUMBER)
ret = true;
});
return ret;
}
void
ircd::m::room::power::for_each(const closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::power::for_each(const closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::power::for_each(const string_view &prop,
const closure &closure)
const
{
for_each(prop, closure_bool{[&closure]
(const string_view &key, const int64_t &level)
{
closure(key, level);
return true;
}});
}
bool
ircd::m::room::power::for_each(const string_view &prop,
const closure_bool &closure)
const
{
bool ret{true};
view([&prop, &closure, &ret]
(const json::object &content)
{
const json::object &collection
{
// This little cmov gimmick sets collection to be the outer object
// itself if no property was given, allowing us to reuse this func
// for all iterations of key -> level mappings.
prop? json::object{content.get(prop)} : content
};
if(prop && json::type(string_view{collection}) != json::OBJECT)
return;
for(auto it(begin(collection)); it != end(collection) && ret; ++it)
{
const auto &member(*it);
if(json::type(member.second) != json::NUMBER)
continue;
const auto &key
{
unquote(member.first)
};
const auto &val
{
lex_cast<int64_t>(member.second)
};
ret = closure(key, val);
}
});
return ret;
}
bool
ircd::m::room::power::view(const std::function<void (const json::object &)> &closure)
const
{
static const event::keys keys
{
event::keys::include
{
"content",
}
};
const m::event::fetch::opts fopts
{
keys, state.fopts? state.fopts->gopts : db::gopts{}
};
return state.get(std::nothrow, "m.room.power_levels", "", [&closure]
(const m::event &event)
{
closure(json::get<"content"_>(event));
});
}