diff --git a/include/ircd/m/room/purge.h b/include/ircd/m/room/purge.h index 29d639f0e..c0f7efe3d 100644 --- a/include/ircd/m/room/purge.h +++ b/include/ircd/m/room/purge.h @@ -12,8 +12,71 @@ #define HAVE_IRCD_M_ROOM_PURGE_H /// Erase the room from the database. Cuidado! +/// +/// The room purge is an application of multiple event::purge operations. +/// By default the entire room is purged. The options can tweak specifics. +/// struct ircd::m::room::purge :returns { - purge(const room &); + struct opts; + + static const struct opts opts_default; + static log::log log; + + private: + const m::room &room; + const struct opts &opts; + db::txn txn; + + bool match(const event::idx &, const event &) const; + bool match(const uint64_t &, const event::idx &) const; + void timeline(); + void state(); + void commit(); + + public: + purge(const m::room &, const struct opts & = opts_default); +}; + +/// Options for purge +struct ircd::m::room::purge::opts +{ + /// Limit purge to the index window + pair idx + { + 0, std::numeric_limits::max() + }; + + /// Limit purge to the depth window + pair depth + { + 0, std::numeric_limits::max() + }; + + /// Limit purge to events matching the filter + const m::event_filter *filter {nullptr}; + + /// Set to false to not purge any state events. + bool state {true}; + + /// Set to false to not purge the present state, but prior (replaced) + /// states will be purged if other options permit. + bool present {true}; + + /// Set to false to not purge replaced states; the only state events + /// considered for purge are present states if other options permit. + bool history {true}; + + /// Timeline in this context refers to non-state events. Set to false to + /// only allow state events to be purged; true to allow non-state events + /// if other options permit. + bool timeline {true}; + + /// Log an INFO message for the final transaction; takes precedence + /// if both debuglog and infolog are true. + bool infolog_txn {false}; + + /// Log a DEBUG message for the final transaction. + bool debuglog_txn {true}; }; diff --git a/include/ircd/m/room/state.h b/include/ircd/m/room/state.h index 45feb5e57..c7184c89a 100644 --- a/include/ircd/m/room/state.h +++ b/include/ircd/m/room/state.h @@ -97,7 +97,6 @@ struct ircd::m::room::state static event::idx next(const event::idx &); static bool present(const event::idx &); - static size_t purge_replaced(const room::id &); static bool is(std::nothrow_t, const event::idx &); static bool is(const event::idx &); }; diff --git a/matrix/Makefile.am b/matrix/Makefile.am index a60feac8c..e7883ae84 100644 --- a/matrix/Makefile.am +++ b/matrix/Makefile.am @@ -122,6 +122,7 @@ libircd_matrix_la_SOURCES += room_content.cc libircd_matrix_la_SOURCES += room_message.cc libircd_matrix_la_SOURCES += room_messages.cc libircd_matrix_la_SOURCES += room_power.cc +libircd_matrix_la_SOURCES += room_purge.cc libircd_matrix_la_SOURCES += room_state.cc libircd_matrix_la_SOURCES += room_state_history.cc libircd_matrix_la_SOURCES += room_state_space.cc diff --git a/matrix/room.cc b/matrix/room.cc index a5485b9d8..575a30a8a 100644 --- a/matrix/room.cc +++ b/matrix/room.cc @@ -8,24 +8,6 @@ // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. -ircd::m::room::purge::purge(const room &room) -:returns{0} -{ - db::txn txn - { - *m::dbs::events - }; - - room.for_each([this, &txn] - (const m::event::idx &event_idx) - { - ret += m::event::purge(txn, event_idx); - }); - - assert(ret || !txn.size()); - txn(); -} - ircd::m::room ircd::m::create(const id::room &room_id, const id::user &creator, diff --git a/matrix/room_purge.cc b/matrix/room_purge.cc new file mode 100644 index 000000000..27afd450c --- /dev/null +++ b/matrix/room_purge.cc @@ -0,0 +1,191 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 Jason Volk +// +// 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. + +decltype(ircd::m::room::purge::opts_default) +ircd::m::room::purge::opts_default; + +decltype(ircd::m::room::purge::log) +ircd::m::room::purge::log +{ + "m.room.purge" +}; + +ircd::m::room::purge::purge(const m::room &room, + const struct opts &opts) +:returns +{ + 0 +} +,room +{ + room +} +,opts +{ + opts +} +,txn +{ + *dbs::events +} +{ + if(opts.timeline) + timeline(); + else if(opts.state) + state(); + + commit(); +} + +void +ircd::m::room::purge::commit() +{ + assert(ret || !txn.size()); + if(!ret) + return; + + if(opts.debuglog_txn || opts.infolog_txn) + log::logf + { + log, opts.infolog_txn? log::level::INFO: log::level::DEBUG, + "Purging %s events:%zu txn[cells:%zu bytes:%zu] opts[st:%b pr:%b hs:%b tl:%b depth[%lu:%lu]]", + string_view{room.room_id}, + ret, + txn.size(), + txn.bytes(), + opts.state, + opts.present, + opts.history, + opts.timeline, + opts.depth.first, + opts.depth.second, + }; + + txn(); +} + +void +ircd::m::room::purge::state() +{ + const room::state::space space + { + room + }; + + space.for_each([this] + (const auto &, const auto &, const auto &depth, const auto &event_idx) + { + if(!match(depth, event_idx)) + return true; + + const event::fetch event + { + std::nothrow, event_idx + }; + + if(unlikely(!event.valid)) + return true; + + if(!match(event_idx, event)) + return true; + + const auto purged + { + event::purge(txn, event_idx, event) + }; + + ret += purged; + return true; + }); +} + +void +ircd::m::room::purge::timeline() +{ + room::events it + { + room, opts.depth.second + }; + + for(; it && it.depth() >= opts.depth.first; --it) + { + if(!match(it.depth(), it.event_idx())) + continue; + + bool valid; + const event &event + { + it.fetch(std::nothrow, &valid) + }; + + if(unlikely(!valid)) + continue; + + if(!match(it.event_idx(), event)) + continue; + + const bool purged + { + event::purge(txn, it.event_idx(), event) + }; + + ret += purged; + } +} + +bool +ircd::m::room::purge::match(const uint64_t &depth, + const event::idx &event_idx) +const +{ + if(depth < opts.depth.first) + return false; + + if(depth > opts.depth.second) + return false; + + if(event_idx < opts.idx.first) + return false; + + if(event_idx > opts.idx.second) + return false; + + return true; +} + +bool +ircd::m::room::purge::match(const event::idx &event_idx, + const event &event) +const +{ + if(!opts.timeline) + if(!defined(json::get<"state_key"_>(event))) + return false; + + if(!opts.state || (!opts.present && !opts.history)) + if(defined(json::get<"state_key"_>(event))) + return false; + + if(opts.filter) + if(!m::match(*opts.filter, event)) + return false; + + if(!opts.present) + if(defined(json::get<"state_key"_>(event))) + if(room::state::present(event_idx)) + return false; + + if(!opts.history) + if(defined(json::get<"state_key"_>(event))) + if(!room::state::present(event_idx)) + return false; + + return true; +} diff --git a/matrix/room_state.cc b/matrix/room_state.cc index f4a139948..1de52d1de 100644 --- a/matrix/room_state.cc +++ b/matrix/room_state.cc @@ -777,39 +777,6 @@ ircd::m::room::state::is(std::nothrow_t, return ret; } -size_t -ircd::m::room::state::purge_replaced(const room::id &room_id) -{ - db::txn txn - { - *m::dbs::events - }; - - size_t ret(0); - m::room::events it - { - room_id, uint64_t(0) - }; - - if(!it) - return ret; - - for(; it; ++it) - { - const m::event::idx &event_idx(it.event_idx()); - static const auto no_action{[](const auto &) noexcept {}}; - if(!m::get(std::nothrow, event_idx, "state_key", no_action)) - continue; - - if(!m::event::refs(event_idx).count(m::dbs::ref::NEXT_STATE)) - continue; - - // TODO: erase event - } - - return ret; -} - bool ircd::m::room::state::present(const event::idx &event_idx) { diff --git a/modules/console.cc b/modules/console.cc index 0c5f9d4b6..dd2470ae6 100644 --- a/modules/console.cc +++ b/modules/console.cc @@ -10938,28 +10938,6 @@ console_cmd__room__state__space__rebuild(opt &out, const string_view &line) return true; } -bool -console_cmd__room__state__purge__replaced(opt &out, const string_view &line) -{ - const params param{line, " ", - { - "room_id", - }}; - - const auto &room_id - { - m::room_id(param.at(0)) - }; - - const size_t ret - { - m::room::state::purge_replaced(room_id) - }; - - out << "erased " << ret << std::endl; - return true; -} - bool console_cmd__room__state__rebuild(opt &out, const string_view &line) { @@ -12257,22 +12235,39 @@ console_cmd__room__purge(opt &out, const string_view &line) { const params param{line, " ", { - "room_id", + "room_id", "op", "start_depth", "end_depth" }}; - const auto &room_id + const auto room_id { m::room_id(param.at(0)) }; - const m::room room - { - room_id - }; - const size_t ret { - m::room::purge(room) + m::room::purge + { + room_id, + { + .depth = + { + param.at("start_depth", 0), + param.at("end_depth", -1UL) + }, + + // When "timeline" is given here we don't purge state + .state = !has(param["op"], "timeline"), + + // When "history" is given here we don't purge present state + .present = !has(param["op"], "history"), + + // When "state" is given here we don't purge non-state + .timeline = !has(param["op"], "state") && !has(param["op"], "history"), + + // Log an INFO message + .infolog_txn = true, + } + } }; out << "erased " << ret << std::endl;