2018-05-15 23:27:26 +02:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
using namespace ircd;
|
|
|
|
|
2018-05-16 21:10:10 +02:00
|
|
|
mapi::header
|
|
|
|
IRCD_MODULE
|
|
|
|
{
|
2018-11-14 09:13:27 +01:00
|
|
|
"Matrix room library; modular components."
|
2018-05-16 21:10:10 +02:00
|
|
|
};
|
|
|
|
|
2019-02-28 23:01:27 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::count_since(const m::room &room,
|
|
|
|
const m::event::idx &a,
|
|
|
|
const m::event::idx &b)
|
2018-09-07 15:20:32 +02:00
|
|
|
{
|
2019-01-10 22:16:41 +01:00
|
|
|
m::room::messages it
|
|
|
|
{
|
|
|
|
room
|
|
|
|
};
|
|
|
|
|
2018-09-07 15:20:32 +02:00
|
|
|
assert(a <= b);
|
|
|
|
it.seek_idx(a);
|
|
|
|
|
2018-12-04 02:32:21 +01:00
|
|
|
if(!it && !exists(room))
|
2018-09-07 15:20:32 +02:00
|
|
|
throw m::NOT_FOUND
|
|
|
|
{
|
|
|
|
"Cannot find room '%s' to count events in",
|
|
|
|
string_view{room.room_id}
|
|
|
|
};
|
|
|
|
else if(!it)
|
|
|
|
throw m::NOT_FOUND
|
|
|
|
{
|
|
|
|
"Event @ idx:%lu or idx:%lu not found in room '%s' or at all",
|
|
|
|
a,
|
|
|
|
b,
|
|
|
|
string_view{room.room_id}
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t ret{0};
|
|
|
|
// Hit the iterator once first otherwise the count will always increment
|
|
|
|
// to `1` erroneously when it ought to show `0`.
|
2019-03-28 07:55:14 +01:00
|
|
|
for(++it; it && it.event_idx() < b; ++it, ++ret);
|
2018-09-07 15:20:32 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-03-05 22:32:52 +01:00
|
|
|
ircd::json::object
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::power::default_content(const mutable_buffer &buf,
|
|
|
|
const m::user::id &creator)
|
|
|
|
{
|
|
|
|
json::stack out{buf};
|
|
|
|
json::stack::object content{out};
|
|
|
|
|
|
|
|
assert(default_power_level == 50);
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "ban", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
json::stack::object
|
|
|
|
{
|
|
|
|
content, "events"
|
|
|
|
};
|
|
|
|
|
|
|
|
assert(default_event_level == 0);
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "events_default", json::value(default_event_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "invite", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "kick", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
json::stack::object notifications
|
|
|
|
{
|
|
|
|
content, "notifications"
|
|
|
|
};
|
|
|
|
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
notifications, "room", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "redact", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "state_default", json::value(default_power_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
json::stack::object users
|
|
|
|
{
|
|
|
|
content, "users"
|
|
|
|
};
|
|
|
|
|
|
|
|
assert(default_creator_level == 100);
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
users, creator, json::value(default_creator_level)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(default_user_level == 0);
|
|
|
|
json::stack::member
|
|
|
|
{
|
|
|
|
content, "users_default", json::value(default_user_level)
|
|
|
|
};
|
|
|
|
|
|
|
|
content.~object();
|
|
|
|
return json::object{out.completed()};
|
|
|
|
}
|
|
|
|
|
2019-02-28 23:07:04 +01:00
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::origins::random(const origins &origins,
|
|
|
|
const closure &view,
|
|
|
|
const closure_bool &proffer)
|
2018-08-23 02:43:20 +02:00
|
|
|
{
|
2018-08-24 13:14:24 +02:00
|
|
|
bool ret{false};
|
2018-08-23 02:43:20 +02:00
|
|
|
const size_t max
|
|
|
|
{
|
|
|
|
origins.count()
|
|
|
|
};
|
|
|
|
|
|
|
|
if(unlikely(!max))
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
auto select
|
|
|
|
{
|
|
|
|
ssize_t(rand::integer(0, max - 1))
|
|
|
|
};
|
|
|
|
|
2019-02-28 23:07:04 +01:00
|
|
|
const closure_bool closure{[&proffer, &view, &select]
|
2018-08-23 02:43:20 +02:00
|
|
|
(const string_view &origin)
|
|
|
|
{
|
|
|
|
if(select-- > 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Test if this random selection is "ok" e.g. the callback allows the
|
|
|
|
// user to test a blacklist for this origin. Skip to next if not.
|
2018-08-24 13:14:24 +02:00
|
|
|
if(proffer && !proffer(origin))
|
2018-08-23 02:43:20 +02:00
|
|
|
{
|
|
|
|
++select;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-24 13:14:24 +02:00
|
|
|
view(origin);
|
2018-08-23 02:43:20 +02:00
|
|
|
return false;
|
|
|
|
}};
|
|
|
|
|
2018-08-24 13:14:24 +02:00
|
|
|
const auto iteration{[&origins, &closure, &ret]
|
2018-08-23 02:43:20 +02:00
|
|
|
{
|
2018-08-24 13:14:24 +02:00
|
|
|
ret = !origins.for_each(closure);
|
2018-08-23 02:43:20 +02:00
|
|
|
}};
|
|
|
|
|
|
|
|
// Attempt select on first iteration
|
|
|
|
iteration();
|
|
|
|
|
|
|
|
// If nothing was OK between the random int and the end of the iteration
|
|
|
|
// then start again and pick the first OK.
|
|
|
|
if(!ret && select >= 0)
|
|
|
|
iteration();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-03-26 02:03:42 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::purge(const room &room)
|
2018-08-22 20:58:45 +02:00
|
|
|
{
|
|
|
|
size_t ret(0);
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
room.for_each([&txn, &ret]
|
|
|
|
(const m::event::idx &idx)
|
|
|
|
{
|
|
|
|
const m::event::fetch event
|
|
|
|
{
|
|
|
|
idx
|
|
|
|
};
|
|
|
|
|
|
|
|
m::dbs::write_opts opts;
|
|
|
|
opts.op = db::op::DELETE;
|
|
|
|
opts.event_idx = idx;
|
|
|
|
m::dbs::write(txn, event, opts);
|
|
|
|
++ret;
|
|
|
|
});
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-03-06 07:49:05 +01:00
|
|
|
void
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::auth::make_refs(const auth &auth,
|
|
|
|
json::stack::array &out,
|
|
|
|
const types &types,
|
|
|
|
const user::id &user_id)
|
2018-06-02 01:11:13 +02:00
|
|
|
{
|
|
|
|
const m::room::state state
|
|
|
|
{
|
2019-03-06 07:49:05 +01:00
|
|
|
auth.room
|
2018-06-02 01:11:13 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const auto fetch{[&out, &state]
|
|
|
|
(const string_view &type, const string_view &state_key)
|
|
|
|
{
|
|
|
|
state.get(std::nothrow, type, state_key, m::event::id::closure{[&out]
|
|
|
|
(const auto &event_id)
|
|
|
|
{
|
|
|
|
json::stack::array auth{out};
|
|
|
|
auth.append(event_id);
|
|
|
|
{
|
|
|
|
json::stack::object hash{auth};
|
|
|
|
json::stack::member will
|
|
|
|
{
|
2018-06-02 02:45:20 +02:00
|
|
|
hash, "", ""
|
2018-06-02 01:11:13 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}});
|
|
|
|
}};
|
|
|
|
|
|
|
|
for(const auto &type : types)
|
|
|
|
fetch(type, "");
|
|
|
|
|
2019-03-06 07:49:05 +01:00
|
|
|
if(user_id)
|
|
|
|
fetch("m.room.member", user_id);
|
2018-06-02 01:11:13 +02:00
|
|
|
}
|
|
|
|
|
2019-03-06 08:19:16 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::head::rebuild(const head &head)
|
2018-06-02 01:10:54 +02:00
|
|
|
{
|
2019-03-06 08:19:16 +01:00
|
|
|
size_t ret{0};
|
|
|
|
const auto &room{head.room};
|
|
|
|
const m::room::state state{room};
|
|
|
|
const auto create_idx
|
|
|
|
{
|
|
|
|
state.get("m.room.create")
|
|
|
|
};
|
|
|
|
|
|
|
|
static const m::event::fetch::opts fopts
|
|
|
|
{
|
|
|
|
{ db::get::NO_CACHE }
|
|
|
|
};
|
|
|
|
|
|
|
|
m::room::messages it
|
|
|
|
{
|
|
|
|
room, create_idx, &fopts
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!it)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
m::dbs::write_opts opts;
|
|
|
|
opts.op = db::op::SET;
|
|
|
|
opts.room_head = true;
|
|
|
|
opts.room_refs = true;
|
|
|
|
for(; it; ++it)
|
|
|
|
{
|
|
|
|
const m::event &event{*it};
|
|
|
|
opts.event_idx = it.event_idx();
|
|
|
|
m::dbs::_index__room_head(txn, event, opts);
|
|
|
|
++ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::head::reset(const head &head)
|
|
|
|
{
|
|
|
|
size_t ret{0};
|
|
|
|
const auto &room{head.room};
|
|
|
|
m::room::messages it{room};
|
|
|
|
if(!it)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
// Replacement will be the single new head
|
|
|
|
const m::event replacement
|
|
|
|
{
|
|
|
|
*it
|
|
|
|
};
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
// Iterate all of the existing heads with a delete operation
|
|
|
|
m::dbs::write_opts opts;
|
|
|
|
opts.op = db::op::DELETE;
|
|
|
|
opts.room_head = true;
|
|
|
|
m::room::head{room}.for_each([&room, &opts, &txn, &ret]
|
|
|
|
(const m::event::idx &event_idx, const m::event::id &event_id)
|
|
|
|
{
|
|
|
|
const m::event::fetch event
|
|
|
|
{
|
|
|
|
event_idx, std::nothrow
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!event.valid)
|
|
|
|
{
|
|
|
|
log::derror
|
|
|
|
{
|
|
|
|
m::log, "Invalid event '%s' idx %lu in head for %s",
|
|
|
|
string_view{event_id},
|
|
|
|
event_idx,
|
|
|
|
string_view{room.room_id}
|
|
|
|
};
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.event_idx = event_idx;
|
|
|
|
m::dbs::_index__room_head(txn, event, opts);
|
|
|
|
++ret;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Finally add the replacement to the txn
|
|
|
|
opts.op = db::op::SET;
|
|
|
|
opts.event_idx = it.event_idx();
|
|
|
|
m::dbs::_index__room_head(txn, replacement, opts);
|
|
|
|
|
|
|
|
// Commit txn
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::head::modify(const m::event::id &event_id,
|
|
|
|
const db::op &op,
|
|
|
|
const bool &refs)
|
|
|
|
{
|
|
|
|
const m::event::fetch event
|
|
|
|
{
|
|
|
|
event_id
|
|
|
|
};
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
// Iterate all of the existing heads with a delete operation
|
|
|
|
m::dbs::write_opts opts;
|
|
|
|
opts.op = op;
|
|
|
|
opts.room_head = true;
|
|
|
|
opts.room_refs = refs;
|
|
|
|
opts.event_idx = event.event_idx;
|
|
|
|
m::dbs::_index__room_head(txn, event, opts);
|
|
|
|
|
|
|
|
// Commit txn
|
|
|
|
txn();
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::head::make_refs(const head &head,
|
|
|
|
json::stack::array &out,
|
|
|
|
const size_t &_limit,
|
|
|
|
const bool &_need_tophead)
|
|
|
|
{
|
|
|
|
size_t limit{_limit};
|
|
|
|
bool need_tophead{_need_tophead};
|
|
|
|
const auto &room{head.room};
|
2018-06-02 01:10:54 +02:00
|
|
|
const auto top_head
|
|
|
|
{
|
|
|
|
need_tophead?
|
|
|
|
m::top(std::nothrow, room.room_id):
|
|
|
|
std::tuple<m::id::event::buf, int64_t, m::event::idx>{}
|
|
|
|
};
|
|
|
|
|
|
|
|
int64_t depth{-1};
|
|
|
|
m::event::fetch event;
|
|
|
|
head.for_each(m::room::head::closure_bool{[&]
|
|
|
|
(const m::event::idx &idx, const m::event::id &event_id)
|
|
|
|
{
|
|
|
|
seek(event, idx, std::nothrow);
|
|
|
|
if(!event.valid)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if(need_tophead)
|
|
|
|
if(json::get<"event_id"_>(event) == std::get<0>(top_head))
|
|
|
|
need_tophead = false;
|
|
|
|
|
|
|
|
depth = std::max(json::get<"depth"_>(event), depth);
|
|
|
|
json::stack::array prev{out};
|
|
|
|
prev.append(event_id);
|
|
|
|
{
|
|
|
|
json::stack::object hash{prev};
|
|
|
|
json::stack::member will
|
|
|
|
{
|
2018-06-02 02:45:20 +02:00
|
|
|
hash, "", ""
|
2018-06-02 01:10:54 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return --limit - need_tophead > 0;
|
|
|
|
}});
|
|
|
|
|
|
|
|
if(need_tophead)
|
|
|
|
{
|
|
|
|
depth = std::get<1>(top_head);
|
|
|
|
json::stack::array prev{out};
|
|
|
|
prev.append(std::get<0>(top_head));
|
|
|
|
{
|
|
|
|
json::stack::object hash{prev};
|
|
|
|
json::stack::member will
|
|
|
|
{
|
2018-06-02 02:45:20 +02:00
|
|
|
hash, "", ""
|
2018-06-02 01:10:54 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return depth;
|
|
|
|
}
|
|
|
|
|
2019-03-06 08:19:16 +01:00
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::head::for_each(const head &head,
|
|
|
|
const closure_bool &closure)
|
2018-06-02 01:10:54 +02:00
|
|
|
{
|
2019-03-06 08:19:16 +01:00
|
|
|
auto it
|
2018-06-02 01:10:54 +02:00
|
|
|
{
|
2019-03-06 08:19:16 +01:00
|
|
|
dbs::room_head.begin(head.room.room_id)
|
|
|
|
};
|
2018-06-02 01:10:54 +02:00
|
|
|
|
2019-03-06 08:19:16 +01:00
|
|
|
for(; it; ++it)
|
2018-06-02 01:10:54 +02:00
|
|
|
{
|
2019-03-06 08:19:16 +01:00
|
|
|
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;
|
2018-06-02 01:10:54 +02:00
|
|
|
}
|
|
|
|
|
2019-02-28 23:19:44 +01:00
|
|
|
std::pair<bool, int64_t>
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::is_complete(const room &room)
|
2019-04-26 09:29:12 +02:00
|
|
|
{
|
|
|
|
std::pair<bool, int64_t> ret {true, -1L};
|
|
|
|
for_each_depth_gap(room, [&ret]
|
|
|
|
(const auto &range)
|
|
|
|
{
|
|
|
|
ret.second = range.second - 1;
|
|
|
|
ret.first = false;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
if(ret.second == -1L)
|
|
|
|
ret.first = false;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::rfor_each_depth_gap(const room &room,
|
|
|
|
const depth_range_closure &closure)
|
2018-05-31 18:18:57 +02:00
|
|
|
{
|
2019-04-23 22:29:49 +02:00
|
|
|
room::messages it
|
2018-05-31 18:18:57 +02:00
|
|
|
{
|
2018-11-30 00:28:37 +01:00
|
|
|
room
|
|
|
|
};
|
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
if(!it)
|
|
|
|
return true;
|
2018-05-31 18:18:57 +02:00
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
for(depth_range range{0L, it.depth()}; it; --it)
|
2018-05-31 18:18:57 +02:00
|
|
|
{
|
2019-04-26 09:29:12 +02:00
|
|
|
range.first = it.depth();
|
|
|
|
if(range.first == range.second)
|
|
|
|
continue;
|
2018-05-31 18:18:57 +02:00
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
--range.second;
|
|
|
|
if(range.first == range.second)
|
2019-04-23 22:29:49 +02:00
|
|
|
continue;
|
2018-05-31 18:18:57 +02:00
|
|
|
|
2019-04-26 09:52:04 +02:00
|
|
|
if(!closure({range.first+1, range.second+1}, it.event_idx()))
|
2019-04-26 09:29:12 +02:00
|
|
|
return false;
|
2018-05-31 18:18:57 +02:00
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
range.second = range.first;
|
2018-05-31 18:18:57 +02:00
|
|
|
}
|
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-04-23 22:29:49 +02:00
|
|
|
|
2019-04-26 09:29:12 +02:00
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::for_each_depth_gap(const room &room,
|
|
|
|
const depth_range_closure &closure)
|
|
|
|
{
|
|
|
|
room::messages it
|
2019-04-23 22:29:49 +02:00
|
|
|
{
|
2019-04-26 09:29:12 +02:00
|
|
|
room, int64_t(0L)
|
2019-04-23 22:29:49 +02:00
|
|
|
};
|
2019-04-26 09:29:12 +02:00
|
|
|
|
|
|
|
for(depth_range range{0L, 0L}; it; ++it)
|
|
|
|
{
|
|
|
|
range.second = it.depth();
|
|
|
|
if(range.first == range.second)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
++range.first;
|
|
|
|
if(range.first == range.second)
|
|
|
|
continue;
|
|
|
|
|
2019-04-26 09:52:04 +02:00
|
|
|
if(!closure(range, it.event_idx()))
|
2019-04-26 09:29:12 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
range.first = range.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-05-31 18:18:57 +02:00
|
|
|
}
|
|
|
|
|
2019-03-26 02:39:30 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::purge_replaced(const state &state)
|
|
|
|
{
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t ret(0);
|
|
|
|
m::room::messages it
|
|
|
|
{
|
|
|
|
state.room_id, uint64_t(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!it)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
for(; it; ++it)
|
|
|
|
{
|
|
|
|
const m::event::idx &event_idx(it.event_idx());
|
|
|
|
if(!m::get(std::nothrow, event_idx, "state_key", [](const auto &) {}))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(!m::event::refs(event_idx).count(m::dbs::ref::STATE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// TODO: erase event
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::force_present(const m::event &event)
|
2018-06-12 08:49:44 +02:00
|
|
|
{
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
if(!defined(json::get<"room_id"_>(event)))
|
|
|
|
throw error
|
|
|
|
{
|
|
|
|
"event %s is not a room event (no room_id)",
|
|
|
|
json::get<"event_id"_>(event)
|
|
|
|
};
|
|
|
|
|
2018-06-12 08:49:44 +02:00
|
|
|
if(!defined(json::get<"state_key"_>(event)))
|
|
|
|
throw error
|
|
|
|
{
|
|
|
|
"event %s is not a state event (no state_key)",
|
|
|
|
json::get<"event_id"_>(event)
|
|
|
|
};
|
|
|
|
|
|
|
|
m::dbs::write_opts opts;
|
2019-02-28 23:46:37 +01:00
|
|
|
opts.event_idx = m::index(event);
|
2018-06-12 08:49:44 +02:00
|
|
|
opts.present = true;
|
|
|
|
opts.history = false;
|
2019-02-06 02:15:17 +01:00
|
|
|
opts.room_head = false;
|
|
|
|
opts.room_refs = false;
|
2018-06-12 08:49:44 +02:00
|
|
|
|
|
|
|
m::dbs::_index__room_state(txn, event, opts);
|
|
|
|
m::dbs::_index__room_joined(txn, event, opts);
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::rebuild_present(const state &state)
|
2018-05-15 23:27:26 +02:00
|
|
|
{
|
|
|
|
size_t ret{0};
|
2019-02-13 23:27:51 +01:00
|
|
|
const auto create_idx
|
2018-05-15 23:27:26 +02:00
|
|
|
{
|
|
|
|
state.get("m.room.create")
|
|
|
|
};
|
|
|
|
|
2018-05-31 18:31:22 +02:00
|
|
|
static const m::event::fetch::opts fopts
|
|
|
|
{
|
|
|
|
{ db::get::NO_CACHE }
|
|
|
|
};
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
const m::room room
|
|
|
|
{
|
|
|
|
state.room_id, nullptr, state.fopts
|
|
|
|
};
|
|
|
|
|
2018-05-15 23:27:26 +02:00
|
|
|
m::room::messages it
|
|
|
|
{
|
2019-02-13 23:27:51 +01:00
|
|
|
room, create_idx, &fopts
|
2018-05-15 23:27:26 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if(!it)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
for(; it; ++it)
|
|
|
|
{
|
|
|
|
const m::event &event{*it};
|
|
|
|
if(!defined(json::get<"state_key"_>(event)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
m::dbs::write_opts opts;
|
2018-05-18 08:47:05 +02:00
|
|
|
opts.event_idx = it.event_idx();
|
2018-05-15 23:27:26 +02:00
|
|
|
opts.present = true;
|
|
|
|
opts.history = false;
|
2019-02-06 02:15:17 +01:00
|
|
|
opts.room_head = false;
|
|
|
|
opts.room_refs = false;
|
2018-05-15 23:27:26 +02:00
|
|
|
|
|
|
|
m::dbs::_index__room_state(txn, event, opts);
|
|
|
|
m::dbs::_index__room_joined(txn, event, opts);
|
|
|
|
++ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
2018-05-18 04:29:44 +02:00
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::rebuild_history(const state &state)
|
2018-05-18 04:29:44 +02:00
|
|
|
{
|
|
|
|
size_t ret{0};
|
2019-02-13 23:27:51 +01:00
|
|
|
const auto create_idx
|
2018-05-18 04:29:44 +02:00
|
|
|
{
|
|
|
|
state.get("m.room.create")
|
|
|
|
};
|
|
|
|
|
2018-05-31 18:31:22 +02:00
|
|
|
static const m::event::fetch::opts fopts
|
|
|
|
{
|
|
|
|
{ db::get::NO_CACHE }
|
|
|
|
};
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
const m::room room
|
|
|
|
{
|
|
|
|
state.room_id, nullptr, state.fopts
|
|
|
|
};
|
|
|
|
|
2018-05-18 04:29:44 +02:00
|
|
|
m::room::messages it
|
|
|
|
{
|
2019-02-13 23:27:51 +01:00
|
|
|
room, create_idx, &fopts
|
2018-05-18 04:29:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if(!it)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
uint r(0);
|
|
|
|
char root[2][64] {0};
|
|
|
|
m::dbs::write_opts opts;
|
|
|
|
opts.root_in = root[++r % 2];
|
|
|
|
opts.root_out = root[++r % 2];
|
|
|
|
opts.present = false;
|
|
|
|
opts.history = true;
|
2019-02-06 02:15:17 +01:00
|
|
|
opts.room_head = false;
|
|
|
|
opts.room_refs = false;
|
2018-05-18 04:29:44 +02:00
|
|
|
|
|
|
|
int64_t depth{0};
|
|
|
|
for(; it; ++it)
|
|
|
|
{
|
|
|
|
const m::event &event{*it};
|
2018-05-18 08:47:05 +02:00
|
|
|
opts.event_idx = it.event_idx();
|
2018-05-18 04:29:44 +02:00
|
|
|
if(at<"depth"_>(event) == depth + 1)
|
|
|
|
++depth;
|
|
|
|
|
|
|
|
if(at<"depth"_>(event) != depth)
|
|
|
|
throw ircd::error
|
|
|
|
{
|
|
|
|
"Incomplete room history: gap between %ld and %ld [%s]",
|
|
|
|
depth,
|
|
|
|
at<"depth"_>(event),
|
|
|
|
string_view{at<"event_id"_>(event)}
|
|
|
|
};
|
|
|
|
|
|
|
|
if(at<"type"_>(event) == "m.room.redaction")
|
|
|
|
{
|
|
|
|
opts.root_in = m::dbs::_index_redact(txn, event, opts);
|
|
|
|
opts.root_out = root[++r % 2];
|
|
|
|
txn();
|
|
|
|
txn.clear();
|
|
|
|
}
|
|
|
|
else if(defined(json::get<"state_key"_>(event)))
|
|
|
|
{
|
|
|
|
opts.root_in = m::dbs::_index_state(txn, event, opts);
|
|
|
|
opts.root_out = root[++r % 2];
|
|
|
|
txn();
|
|
|
|
txn.clear();
|
|
|
|
}
|
2019-01-15 22:00:15 +01:00
|
|
|
else m::dbs::_index_other(txn, event, opts);
|
2018-05-18 04:29:44 +02:00
|
|
|
|
|
|
|
++ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
2018-05-22 11:58:21 +02:00
|
|
|
|
2018-11-14 02:33:18 +01:00
|
|
|
//TODO: state btree.
|
2019-02-28 23:46:37 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::clear_history(const state &state)
|
2018-11-14 02:33:18 +01:00
|
|
|
{
|
|
|
|
static const db::gopts gopts
|
|
|
|
{
|
|
|
|
db::get::NO_CACHE
|
|
|
|
};
|
|
|
|
|
|
|
|
db::txn txn
|
|
|
|
{
|
|
|
|
*m::dbs::events
|
|
|
|
};
|
|
|
|
|
|
|
|
auto it
|
|
|
|
{
|
2019-02-28 23:46:37 +01:00
|
|
|
m::dbs::room_events.begin(state.room_id, gopts)
|
2018-11-14 02:33:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
size_t ret{0};
|
|
|
|
for(; it; ++it, ret++)
|
|
|
|
{
|
|
|
|
const auto pair
|
|
|
|
{
|
|
|
|
m::dbs::room_events_key(it->first)
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto &depth{std::get<0>(pair)};
|
|
|
|
const auto &event_idx{std::get<1>(pair)};
|
|
|
|
thread_local char buf[m::dbs::ROOM_EVENTS_KEY_MAX_SIZE];
|
|
|
|
const string_view key
|
|
|
|
{
|
2019-02-28 23:46:37 +01:00
|
|
|
m::dbs::room_events_key(buf, state.room_id, depth, event_idx)
|
2018-11-14 02:33:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
db::txn::append
|
|
|
|
{
|
|
|
|
txn, m::dbs::room_events,
|
|
|
|
{
|
|
|
|
db::op::SET,
|
|
|
|
key
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
txn();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-10-27 23:03:32 +02:00
|
|
|
conf::item<ulong>
|
|
|
|
state__prefetch__yield_modulus
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.room.state_prefetch.yield_modulus" },
|
|
|
|
{ "default", 256L },
|
|
|
|
};
|
|
|
|
|
2019-02-28 23:46:37 +01:00
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::prefetch(const state &state,
|
|
|
|
const string_view &type,
|
|
|
|
const event::idx_range &range)
|
2018-10-23 18:54:45 +02:00
|
|
|
{
|
|
|
|
const m::event::fetch::opts &fopts
|
|
|
|
{
|
|
|
|
state.fopts?
|
|
|
|
*state.fopts:
|
|
|
|
m::event::fetch::default_opts
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t ret{0};
|
|
|
|
state.for_each(type, m::event::closure_idx{[&ret, &fopts, &range]
|
|
|
|
(const m::event::idx &event_idx)
|
|
|
|
{
|
|
|
|
if(event_idx < range.first)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(range.second && event_idx > range.second)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m::prefetch(event_idx, fopts);
|
|
|
|
++ret;
|
2018-10-27 23:03:32 +02:00
|
|
|
|
|
|
|
const ulong ym(state__prefetch__yield_modulus);
|
|
|
|
if(ym && ret % ym == 0)
|
|
|
|
ctx::yield();
|
2018-10-23 18:54:45 +02:00
|
|
|
}});
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-03-11 23:01:14 +01:00
|
|
|
ircd::m::event::idx
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::prev(const event::idx &event_idx)
|
|
|
|
{
|
|
|
|
event::idx ret{0};
|
|
|
|
prev(event_idx, [&ret]
|
|
|
|
(const event::idx &event_idx)
|
|
|
|
{
|
|
|
|
if(event_idx > ret)
|
|
|
|
ret = event_idx;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::m::event::idx
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::next(const event::idx &event_idx)
|
|
|
|
{
|
|
|
|
event::idx ret{0};
|
|
|
|
next(event_idx, [&ret]
|
|
|
|
(const event::idx &event_idx)
|
|
|
|
{
|
|
|
|
if(event_idx > ret)
|
|
|
|
ret = event_idx;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::next(const event::idx &event_idx,
|
|
|
|
const event::closure_idx_bool &closure)
|
|
|
|
{
|
|
|
|
const m::event::refs refs
|
|
|
|
{
|
|
|
|
event_idx
|
|
|
|
};
|
|
|
|
|
|
|
|
return refs.for_each(dbs::ref::STATE, [&closure]
|
|
|
|
(const event::idx &event_idx, const dbs::ref &ref)
|
|
|
|
{
|
|
|
|
assert(ref == dbs::ref::STATE);
|
|
|
|
return closure(event_idx);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::state::prev(const event::idx &event_idx,
|
|
|
|
const event::closure_idx_bool &closure)
|
|
|
|
{
|
|
|
|
const m::event::refs refs
|
|
|
|
{
|
|
|
|
event_idx
|
|
|
|
};
|
|
|
|
|
|
|
|
return refs.for_each(dbs::ref::PREV_STATE, [&closure]
|
|
|
|
(const event::idx &event_idx, const dbs::ref &ref)
|
|
|
|
{
|
|
|
|
assert(ref == dbs::ref::PREV_STATE);
|
|
|
|
return closure(event_idx);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-23 11:03:29 +02:00
|
|
|
extern "C" size_t
|
|
|
|
dagree_histogram(const m::room &room,
|
|
|
|
std::vector<size_t> &vec)
|
|
|
|
{
|
|
|
|
static const m::event::fetch::opts fopts
|
|
|
|
{
|
2018-05-31 18:31:22 +02:00
|
|
|
{ db::get::NO_CACHE },
|
2018-05-23 11:03:29 +02:00
|
|
|
m::event::keys::include
|
|
|
|
{
|
|
|
|
"event_id",
|
|
|
|
"prev_events",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
m::room::messages it
|
|
|
|
{
|
|
|
|
room, &fopts
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t ret{0};
|
|
|
|
for(; it; --it)
|
|
|
|
{
|
|
|
|
const m::event event{*it};
|
2018-05-24 12:46:54 +02:00
|
|
|
const size_t num{degree(event)};
|
2018-05-23 11:03:29 +02:00
|
|
|
if(unlikely(num >= vec.size()))
|
|
|
|
{
|
|
|
|
log::warning
|
|
|
|
{
|
2018-12-23 02:44:18 +01:00
|
|
|
m::log, "Event '%s' had %zu prev events (ignored)",
|
2018-05-23 11:03:29 +02:00
|
|
|
string_view(at<"event_id"_>(event))
|
|
|
|
};
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
++vec[num];
|
|
|
|
++ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2018-06-02 02:20:35 +02:00
|
|
|
|
|
|
|
extern "C" void
|
|
|
|
room_herd(const m::room &room,
|
|
|
|
const m::user &user,
|
|
|
|
const milliseconds &timeout)
|
|
|
|
{
|
|
|
|
using closure_prototype = bool (const string_view &,
|
|
|
|
std::exception_ptr,
|
|
|
|
const json::object &);
|
|
|
|
|
|
|
|
using prototype = void (const m::room::id &,
|
|
|
|
const m::user::id &,
|
|
|
|
const milliseconds &,
|
|
|
|
const std::function<closure_prototype> &);
|
|
|
|
|
2018-09-14 16:39:11 +02:00
|
|
|
static mods::import<prototype> feds__head
|
2018-06-02 02:20:35 +02:00
|
|
|
{
|
|
|
|
"federation_federation", "feds__head"
|
|
|
|
};
|
|
|
|
|
|
|
|
std::set<std::string> event_ids;
|
|
|
|
feds__head(room.room_id, user.user_id, timeout, [&event_ids]
|
|
|
|
(const string_view &origin, std::exception_ptr eptr, const json::object &event)
|
|
|
|
{
|
|
|
|
if(eptr)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
const json::array prev_events
|
|
|
|
{
|
|
|
|
event.at("prev_events")
|
|
|
|
};
|
|
|
|
|
|
|
|
for(const json::array prev_event : prev_events)
|
|
|
|
{
|
|
|
|
const auto &prev_event_id
|
|
|
|
{
|
|
|
|
unquote(prev_event.at(0))
|
|
|
|
};
|
|
|
|
|
|
|
|
event_ids.emplace(prev_event_id);
|
|
|
|
};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2019-03-01 20:35:07 +01:00
|
|
|
size_t i(0);
|
2018-06-02 02:20:35 +02:00
|
|
|
for(const m::event::id &event_id : event_ids)
|
|
|
|
if(exists(event_id))
|
|
|
|
{
|
2019-03-06 08:19:16 +01:00
|
|
|
m::room::head::modify(event_id, db::op::SET, false);
|
2018-06-02 02:20:35 +02:00
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
const m::room::head head
|
|
|
|
{
|
|
|
|
room
|
|
|
|
};
|
|
|
|
|
2019-03-01 20:35:07 +01:00
|
|
|
for(; i + 1 >= 1 && head.count() > 1; --i)
|
2018-06-02 02:20:35 +02:00
|
|
|
{
|
|
|
|
const auto eid
|
|
|
|
{
|
|
|
|
send(room, user, "ircd.room.revelation", json::object{})
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx::sleep(seconds(2));
|
|
|
|
}
|
|
|
|
}
|
2019-02-12 20:03:59 +01:00
|
|
|
|
|
|
|
bool
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::auth::for_each(const auth &a,
|
|
|
|
const closure_bool &closure)
|
|
|
|
{
|
|
|
|
const m::room &room{a.room};
|
|
|
|
const auto &event_id{room.event_id};
|
|
|
|
if(!event_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-02-26 01:28:53 +01:00
|
|
|
|
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
__attribute__((noreturn))
|
|
|
|
ircd::m::room::stats::bytes_total(const m::room &room)
|
|
|
|
{
|
|
|
|
throw m::UNSUPPORTED
|
|
|
|
{
|
|
|
|
"Not yet implemented."
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
__attribute__((noreturn))
|
|
|
|
ircd::m::room::stats::bytes_total_compressed(const m::room &room)
|
|
|
|
{
|
|
|
|
throw m::UNSUPPORTED
|
|
|
|
{
|
|
|
|
"Not yet implemented."
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
ircd::m::room::stats::bytes_json(const m::room &room)
|
|
|
|
{
|
|
|
|
size_t ret(0);
|
|
|
|
for(m::room::messages it(room); it; --it)
|
|
|
|
{
|
|
|
|
const m::event::idx &event_idx
|
|
|
|
{
|
|
|
|
it.event_idx()
|
|
|
|
};
|
|
|
|
|
|
|
|
const byte_view<string_view> key
|
|
|
|
{
|
|
|
|
event_idx
|
|
|
|
};
|
|
|
|
|
|
|
|
static const db::gopts gopts
|
|
|
|
{
|
|
|
|
db::get::NO_CACHE
|
|
|
|
};
|
|
|
|
|
|
|
|
ret += db::bytes_value(m::dbs::event_json, key, gopts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
IRCD_MODULE_EXPORT
|
|
|
|
__attribute__((noreturn))
|
|
|
|
ircd::m::room::stats::bytes_json_compressed(const m::room &room)
|
|
|
|
{
|
|
|
|
throw m::UNSUPPORTED
|
|
|
|
{
|
|
|
|
"Not yet implemented."
|
|
|
|
};
|
|
|
|
}
|