mirror of
https://github.com/matrix-construct/construct
synced 2024-11-05 05:18:54 +01:00
1018 lines
20 KiB
C++
1018 lines
20 KiB
C++
// 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.
|
|
|
|
decltype(ircd::m::room::state::enable_history)
|
|
ircd::m::room::state::enable_history
|
|
{
|
|
{ "name", "ircd.m.room.state.enable_history" },
|
|
{ "default", true },
|
|
};
|
|
|
|
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{}
|
|
}
|
|
,fopts
|
|
{
|
|
fopts?
|
|
fopts:
|
|
room.fopts
|
|
}
|
|
{
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::prefetch(const string_view &type)
|
|
const
|
|
{
|
|
return prefetch(type, string_view{});
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::prefetch(const string_view &type,
|
|
const string_view &state_key)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.prefetch(type, state_key);
|
|
}
|
|
|
|
char buf[dbs::ROOM_STATE_KEY_MAX_SIZE];
|
|
const auto &key
|
|
{
|
|
dbs::room_state_key(buf, room_id, type, state_key)
|
|
};
|
|
|
|
return db::prefetch(dbs::room_state, key);
|
|
}
|
|
|
|
ircd::m::event::idx
|
|
ircd::m::room::state::get(const string_view &type,
|
|
const string_view &state_key)
|
|
const
|
|
{
|
|
event::idx ret;
|
|
get(type, state_key, event::closure_idx{[&ret]
|
|
(const event::idx &event_idx)
|
|
{
|
|
ret = event_idx;
|
|
}});
|
|
|
|
return ret;
|
|
}
|
|
|
|
ircd::m::event::idx
|
|
ircd::m::room::state::get(std::nothrow_t,
|
|
const string_view &type,
|
|
const string_view &state_key)
|
|
const
|
|
{
|
|
event::idx ret{0};
|
|
get(std::nothrow, type, state_key, event::closure_idx{[&ret]
|
|
(const event::idx &event_idx)
|
|
{
|
|
ret = event_idx;
|
|
}});
|
|
|
|
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
|
|
{
|
|
get(type, state_key, event::closure_idx{[&]
|
|
(const event::idx &idx)
|
|
{
|
|
if(!m::event_id(std::nothrow, idx, closure))
|
|
throw m::NOT_FOUND
|
|
{
|
|
"(%s,%s) in %s idx:%lu event_id :not found",
|
|
type,
|
|
state_key,
|
|
string_view{room_id},
|
|
idx,
|
|
};
|
|
}});
|
|
}
|
|
|
|
void
|
|
ircd::m::room::state::get(const string_view &type,
|
|
const string_view &state_key,
|
|
const event::closure_idx &closure)
|
|
const try
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
closure(history.get(type, state_key));
|
|
return;
|
|
}
|
|
|
|
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
|
|
{
|
|
std::nothrow, event_idx, 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
|
|
{
|
|
return get(std::nothrow, type, state_key, event::closure_idx{[&closure]
|
|
(const event::idx &idx)
|
|
{
|
|
m::event_id(std::nothrow, idx, 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())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
const auto event_idx
|
|
{
|
|
history.get(std::nothrow, type, state_key)
|
|
};
|
|
|
|
if(event_idx)
|
|
{
|
|
closure(event_idx);
|
|
return true;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
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 event::idx &event_idx)
|
|
const
|
|
{
|
|
static const event::fetch::opts fopts
|
|
{
|
|
event::keys::include { "type", "state_key" },
|
|
};
|
|
|
|
const m::event::fetch event
|
|
{
|
|
std::nothrow, event_idx, fopts
|
|
};
|
|
|
|
if(!event.valid)
|
|
return false;
|
|
|
|
const auto state_idx
|
|
{
|
|
get(std::nothrow, at<"type"_>(event), at<"state_key"_>(event))
|
|
};
|
|
|
|
assert(event_idx);
|
|
return event_idx == state_idx;
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::has(const string_view &type)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.has(type);
|
|
}
|
|
|
|
return !for_each(type, []
|
|
(const string_view &, const string_view &, const event::idx &)
|
|
{
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::has(const string_view &type,
|
|
const string_view &state_key)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.has(type, state_key);
|
|
}
|
|
|
|
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
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.count(string_view{});
|
|
}
|
|
|
|
const db::gopts &opts
|
|
{
|
|
this->fopts?
|
|
this->fopts->gopts:
|
|
db::gopts{}
|
|
};
|
|
|
|
size_t ret(0);
|
|
auto &column{dbs::room_state};
|
|
for(auto it{column.begin(room_id, opts)}; bool(it); ++it)
|
|
++ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t
|
|
ircd::m::room::state::count(const string_view &type)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.count(type);
|
|
}
|
|
|
|
size_t ret(0);
|
|
for_each(type, [&ret]
|
|
(const string_view &, const string_view &, const event::idx &)
|
|
{
|
|
++ret;
|
|
return true;
|
|
});
|
|
|
|
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(std::nothrow, event, event_idx))
|
|
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
|
|
{
|
|
return for_each(event::closure_idx_bool{[&closure]
|
|
(const event::idx &idx)
|
|
{
|
|
bool ret{true};
|
|
m::event_id(std::nothrow, idx, [&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
|
|
{
|
|
return for_each(closure_bool{[&closure]
|
|
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
|
|
{
|
|
return closure(event_idx);
|
|
}});
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::for_each(const closure_bool &closure)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.for_each([&closure]
|
|
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
|
|
{
|
|
return closure(type, state_key, event_idx);
|
|
});
|
|
}
|
|
|
|
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)
|
|
{
|
|
const byte_view<event::idx> idx(it->second);
|
|
const auto key(dbs::room_state_key(it->first));
|
|
if(!closure(std::get<0>(key), std::get<1>(key), idx))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::for_each(const type_prefix &prefix,
|
|
const closure_bool &closure)
|
|
const
|
|
{
|
|
bool ret(true), cont(true);
|
|
for_each(closure_bool{[&prefix, &closure, &ret, &cont]
|
|
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
|
|
{
|
|
if(!startswith(type, string_view(prefix)))
|
|
return cont;
|
|
|
|
cont = false;
|
|
ret = closure(type, state_key, event_idx);
|
|
return ret;
|
|
}});
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 closure_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(std::nothrow, event, event_idx))
|
|
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
|
|
{
|
|
return for_each(type, state_key_lb, event::closure_idx_bool{[&closure]
|
|
(const event::idx &idx)
|
|
{
|
|
bool ret{true};
|
|
m::event_id(std::nothrow, idx, [&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
|
|
{
|
|
return for_each(type, state_key_lb, closure_bool{[&closure]
|
|
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
|
|
{
|
|
return closure(event_idx);
|
|
}});
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::for_each(const string_view &type,
|
|
const string_view &state_key_lb,
|
|
const closure_bool &closure)
|
|
const
|
|
{
|
|
if(!present())
|
|
{
|
|
const history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
return history.for_each(type, state_key_lb, [&closure]
|
|
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
|
|
{
|
|
return closure(type, state_key, event_idx);
|
|
});
|
|
}
|
|
|
|
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)
|
|
break;
|
|
|
|
const byte_view<event::idx> idx(it->second);
|
|
if(!closure(std::get<0>(key), std::get<1>(key), idx))
|
|
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 the global configuration disables history, always consider the
|
|
// present state. (disabling may yield unexpected incorrect results by
|
|
// returning the present state without error).
|
|
if(!enable_history)
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::is(const event::idx &event_idx)
|
|
{
|
|
bool ret{false};
|
|
m::get(event_idx, "state_key", [&ret]
|
|
(const string_view &state_key)
|
|
{
|
|
ret = true;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
ircd::m::room::state::is(std::nothrow_t,
|
|
const event::idx &event_idx)
|
|
{
|
|
bool ret{false};
|
|
m::get(std::nothrow, event_idx, "state_key", [&ret]
|
|
(const string_view &state_key)
|
|
{
|
|
ret = true;
|
|
});
|
|
|
|
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());
|
|
if(!m::get(std::nothrow, event_idx, "state_key", [](const auto &) {}))
|
|
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)
|
|
{
|
|
static const event::fetch::opts fopts
|
|
{
|
|
event::keys::include { "room_id", "type", "state_key" },
|
|
};
|
|
|
|
const m::event::fetch event
|
|
{
|
|
event_idx, fopts
|
|
};
|
|
|
|
const m::room room
|
|
{
|
|
at<"room_id"_>(event)
|
|
};
|
|
|
|
const m::room::state state
|
|
{
|
|
room
|
|
};
|
|
|
|
const auto state_idx
|
|
{
|
|
state.get(std::nothrow, at<"type"_>(event), at<"state_key"_>(event))
|
|
};
|
|
|
|
assert(event_idx);
|
|
return state_idx == event_idx;
|
|
}
|
|
|
|
ircd::m::event::idx
|
|
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::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::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::NEXT_STATE, [&closure]
|
|
(const event::idx &event_idx, const dbs::ref &ref)
|
|
{
|
|
assert(ref == dbs::ref::NEXT_STATE);
|
|
return closure(event_idx);
|
|
});
|
|
}
|
|
|
|
bool
|
|
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);
|
|
});
|
|
}
|
|
|
|
//
|
|
// state::rebuild
|
|
//
|
|
|
|
ircd::m::room::state::rebuild::rebuild(const room::id &room_id)
|
|
{
|
|
const m::event::id::buf event_id
|
|
{
|
|
m::head(room_id)
|
|
};
|
|
|
|
const m::room::state::history history
|
|
{
|
|
room_id, event_id
|
|
};
|
|
|
|
const m::room::state present_state
|
|
{
|
|
room_id
|
|
};
|
|
|
|
const bool check_auth
|
|
{
|
|
!m::internal(room_id)
|
|
};
|
|
|
|
m::dbs::write_opts opts;
|
|
opts.appendix.reset();
|
|
opts.appendix.set(dbs::appendix::ROOM_STATE);
|
|
opts.appendix.set(dbs::appendix::ROOM_JOINED);
|
|
db::txn txn
|
|
{
|
|
*m::dbs::events
|
|
};
|
|
|
|
ssize_t deleted(0);
|
|
present_state.for_each([&opts, &txn, &deleted]
|
|
(const auto &type, const auto &state_key, const auto &event_idx)
|
|
{
|
|
const m::event::fetch &event
|
|
{
|
|
std::nothrow, event_idx
|
|
};
|
|
|
|
if(!event.valid)
|
|
return true;
|
|
|
|
auto _opts(opts);
|
|
_opts.op = db::op::DELETE;
|
|
_opts.event_idx = event_idx;
|
|
dbs::write(txn, event, _opts);
|
|
++deleted;
|
|
return true;
|
|
});
|
|
|
|
ssize_t added(0);
|
|
history.for_each([&opts, &txn, &added, &room_id, &check_auth]
|
|
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
|
|
{
|
|
const m::event::fetch &event
|
|
{
|
|
std::nothrow, event_idx
|
|
};
|
|
|
|
if(!event.valid)
|
|
return true;
|
|
|
|
const auto &[pass, fail]
|
|
{
|
|
check_auth?
|
|
auth::check_present(event):
|
|
room::auth::passfail{true, {}}
|
|
};
|
|
|
|
if(!pass)
|
|
{
|
|
log::dwarning
|
|
{
|
|
log, "%s fails for present state in %s :%s",
|
|
string_view{event.event_id},
|
|
string_view{room_id},
|
|
what(fail),
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
auto _opts(opts);
|
|
_opts.op = db::op::SET;
|
|
_opts.event_idx = event_idx;
|
|
dbs::write(txn, event, _opts);
|
|
++added;
|
|
return true;
|
|
});
|
|
|
|
log::info
|
|
{
|
|
log, "Present state of %s @ %s rebuild complete with %zu size:%s del:%zd add:%zd (%zd)",
|
|
string_view{room_id},
|
|
string_view{event_id},
|
|
txn.size(),
|
|
pretty(iec(txn.bytes())),
|
|
deleted,
|
|
added,
|
|
(added - deleted),
|
|
};
|
|
|
|
txn();
|
|
}
|