mirror of
https://github.com/matrix-construct/construct
synced 2024-12-26 15:33:54 +01:00
ircd:Ⓜ️ Captain hook.
This commit is contained in:
parent
da8ef9e08e
commit
bd14377904
4 changed files with 350 additions and 1 deletions
91
include/ircd/m/hook.h
Normal file
91
include/ircd/m/hook.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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.
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_HOOK_H
|
||||
|
||||
namespace ircd::m
|
||||
{
|
||||
struct hook;
|
||||
}
|
||||
|
||||
struct ircd::m::hook
|
||||
{
|
||||
struct site;
|
||||
struct list;
|
||||
|
||||
IRCD_EXCEPTION(ircd::error, error)
|
||||
|
||||
struct list static list;
|
||||
|
||||
json::strung _feature;
|
||||
json::object feature;
|
||||
std::function<void (const m::event &)> function;
|
||||
bool registered;
|
||||
|
||||
string_view site_name() const;
|
||||
|
||||
public:
|
||||
hook(const json::members &, decltype(function));
|
||||
hook(hook &&) = delete;
|
||||
hook(const hook &) = delete;
|
||||
virtual ~hook() noexcept;
|
||||
};
|
||||
|
||||
/// The hook::site is the call-site for a hook. Each hook site is named
|
||||
/// and registers itself with the master extern hook::list. Each hook
|
||||
/// then registers itself with a hook::site. The site contains internal
|
||||
/// state to manage the efficient calling of the participating hooks.
|
||||
struct ircd::m::hook::site
|
||||
{
|
||||
json::strung _feature;
|
||||
json::object feature;
|
||||
bool registered;
|
||||
|
||||
string_view name() const;
|
||||
|
||||
std::multimap<string_view, hook *> origin;
|
||||
std::multimap<string_view, hook *> room_id;
|
||||
std::multimap<string_view, hook *> sender;
|
||||
std::multimap<string_view, hook *> state_key;
|
||||
std::multimap<string_view, hook *> type;
|
||||
|
||||
friend class hook;
|
||||
bool add(hook &);
|
||||
bool del(hook &);
|
||||
|
||||
public:
|
||||
void operator()(const event &);
|
||||
|
||||
site(const json::members &);
|
||||
site(site &&) = delete;
|
||||
site(const site &) = delete;
|
||||
~site() noexcept;
|
||||
};
|
||||
|
||||
/// The hook list is the registry of all of the hook sites. This is a singleton
|
||||
/// class with an extern instance. Each hook::site will register itself here
|
||||
/// by human readable name.
|
||||
struct ircd::m::hook::list
|
||||
:std::map<string_view, hook::site *>
|
||||
{
|
||||
friend class site;
|
||||
bool add(site &);
|
||||
bool del(site &);
|
||||
|
||||
friend class hook;
|
||||
bool add(hook &);
|
||||
bool del(hook &);
|
||||
|
||||
public:
|
||||
list() = default;
|
||||
list(list &&) = delete;
|
||||
list(const list &) = delete;
|
||||
};
|
|
@ -57,6 +57,7 @@ namespace ircd
|
|||
#include "vm.h"
|
||||
#include "keys.h"
|
||||
#include "txn.h"
|
||||
#include "hook.h"
|
||||
|
||||
struct ircd::m::init
|
||||
{
|
||||
|
|
|
@ -107,9 +107,9 @@ libircd_la_SOURCES = \
|
|||
m/filter.cc \
|
||||
m/request.cc \
|
||||
m/v1.cc \
|
||||
m/vm.cc \
|
||||
m/keys.cc \
|
||||
m/m.cc \
|
||||
m/vm.cc \
|
||||
ircd.cc \
|
||||
###
|
||||
|
||||
|
|
257
ircd/m/m.cc
257
ircd/m/m.cc
|
@ -459,3 +459,260 @@ ircd::m::commit(const room &room,
|
|||
|
||||
return function(room, event, contents);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// m/hook.h
|
||||
//
|
||||
|
||||
ircd::m::hook::hook(const json::members &members,
|
||||
decltype(function) function)
|
||||
try
|
||||
:_feature
|
||||
{
|
||||
members
|
||||
}
|
||||
,feature
|
||||
{
|
||||
_feature
|
||||
}
|
||||
,function
|
||||
{
|
||||
std::move(function)
|
||||
}
|
||||
,registered
|
||||
{
|
||||
list.add(*this)
|
||||
}
|
||||
{
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if(registered)
|
||||
list.del(*this);
|
||||
}
|
||||
|
||||
ircd::m::hook::~hook()
|
||||
noexcept
|
||||
{
|
||||
if(registered)
|
||||
list.del(*this);
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::hook::site_name()
|
||||
const try
|
||||
{
|
||||
return unquote(feature.at("_site"));
|
||||
}
|
||||
catch(const std::out_of_range &e)
|
||||
{
|
||||
throw assertive
|
||||
{
|
||||
"Hook %p must name a '_site' to register with.", this
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// hook::site
|
||||
//
|
||||
|
||||
ircd::m::hook::site::site(const json::members &members)
|
||||
try
|
||||
:_feature
|
||||
{
|
||||
members
|
||||
}
|
||||
,feature
|
||||
{
|
||||
_feature
|
||||
}
|
||||
,registered
|
||||
{
|
||||
list.add(*this)
|
||||
}
|
||||
{
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if(registered)
|
||||
list.del(*this);
|
||||
}
|
||||
|
||||
ircd::m::hook::site::~site()
|
||||
noexcept
|
||||
{
|
||||
if(registered)
|
||||
list.del(*this);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::hook::site::operator()(const event &event)
|
||||
{
|
||||
std::set<hook *> matching; //TODO: allocator
|
||||
const auto match{[&matching]
|
||||
(auto &map, const string_view &key)
|
||||
{
|
||||
auto pit{map.equal_range(key)};
|
||||
for(; pit.first != pit.second; ++pit.first)
|
||||
matching.emplace(pit.first->second);
|
||||
}};
|
||||
|
||||
match(origin, at<"origin"_>(event));
|
||||
match(room_id, at<"room_id"_>(event));
|
||||
match(sender, at<"sender"_>(event));
|
||||
match(type, at<"type"_>(event));
|
||||
|
||||
if(json::get<"state_key"_>(event))
|
||||
match(state_key, at<"state_key"_>(event));
|
||||
|
||||
for(const auto &hook : matching)
|
||||
hook->function(event);
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::hook::site::add(hook &hook)
|
||||
{
|
||||
// Note that m::event property names are first class members of
|
||||
// the hook feature which is why our property names use _underscore.
|
||||
const m::event feature
|
||||
{
|
||||
hook.feature
|
||||
};
|
||||
|
||||
if(json::get<"origin"_>(feature))
|
||||
origin.emplace(at<"origin"_>(feature), &hook);
|
||||
|
||||
if(json::get<"room_id"_>(feature))
|
||||
room_id.emplace(at<"room_id"_>(feature), &hook);
|
||||
|
||||
if(json::get<"sender"_>(feature))
|
||||
sender.emplace(at<"sender"_>(feature), &hook);
|
||||
|
||||
if(json::get<"state_key"_>(feature))
|
||||
state_key.emplace(at<"state_key"_>(feature), &hook);
|
||||
|
||||
if(json::get<"type"_>(feature))
|
||||
type.emplace(at<"type"_>(feature), &hook);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::hook::site::del(hook &hook)
|
||||
{
|
||||
const m::event feature
|
||||
{
|
||||
hook.feature
|
||||
};
|
||||
|
||||
const auto unmap{[&hook]
|
||||
(auto &map, const string_view &key)
|
||||
{
|
||||
auto pit{map.equal_range(key)};
|
||||
for(; pit.first != pit.second; ++pit.first)
|
||||
if(pit.first->second == &hook)
|
||||
return map.erase(pit.first);
|
||||
|
||||
assert(0);
|
||||
}};
|
||||
|
||||
if(json::get<"origin"_>(feature))
|
||||
unmap(origin, at<"origin"_>(feature));
|
||||
|
||||
if(json::get<"room_id"_>(feature))
|
||||
unmap(room_id, at<"room_id"_>(feature));
|
||||
|
||||
if(json::get<"sender"_>(feature))
|
||||
unmap(sender, at<"sender"_>(feature));
|
||||
|
||||
if(json::get<"state_key"_>(feature))
|
||||
unmap(state_key, at<"state_key"_>(feature));
|
||||
|
||||
if(json::get<"type"_>(feature))
|
||||
unmap(type, at<"type"_>(feature));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::hook::site::name()
|
||||
const try
|
||||
{
|
||||
return unquote(feature.at("name"));
|
||||
}
|
||||
catch(const std::out_of_range &e)
|
||||
{
|
||||
throw assertive
|
||||
{
|
||||
"Hook site %p requires a name", this
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// hook::list
|
||||
//
|
||||
|
||||
decltype(ircd::m::hook::list)
|
||||
ircd::m::hook::list
|
||||
{};
|
||||
|
||||
bool
|
||||
ircd::m::hook::list::del(hook &hook)
|
||||
try
|
||||
{
|
||||
const auto site(at(hook.site_name()));
|
||||
assert(site != nullptr);
|
||||
return site->add(hook);
|
||||
}
|
||||
catch(const std::out_of_range &e)
|
||||
{
|
||||
log::critical
|
||||
{
|
||||
"Tried to unregister hook(%p) from missing hook::site '%s'",
|
||||
&hook,
|
||||
hook.site_name()
|
||||
};
|
||||
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::hook::list::add(hook &hook)
|
||||
try
|
||||
{
|
||||
const auto site(at(hook.site_name()));
|
||||
assert(site != nullptr);
|
||||
return site->add(hook);
|
||||
}
|
||||
catch(const std::out_of_range &e)
|
||||
{
|
||||
throw error
|
||||
{
|
||||
"No hook::site named '%s' is registered...", hook.site_name()
|
||||
};
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::hook::list::del(site &site)
|
||||
{
|
||||
return erase(site.name());
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::hook::list::add(site &site)
|
||||
{
|
||||
const auto iit
|
||||
{
|
||||
emplace(site.name(), &site)
|
||||
};
|
||||
|
||||
if(unlikely(!iit.second))
|
||||
throw error
|
||||
{
|
||||
"Hook site name '%s' already in use", site.name()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue