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

ircd:Ⓜ️:typing: Move remaining assets into namespace; minor reorg.

This commit is contained in:
Jason Volk 2020-04-25 16:45:35 -07:00
parent ea97994fe3
commit f9df9bfbda
4 changed files with 353 additions and 373 deletions

View file

@ -11,11 +11,30 @@
#pragma once #pragma once
#define HAVE_IRCD_M_TYPING_H #define HAVE_IRCD_M_TYPING_H
namespace ircd::m namespace ircd::m::typing
{ {
struct typing; struct typist;
struct commit;
using edu = m::edu::m_typing;
using closure = std::function<bool (const edu &)>;
// Iterate all of the active typists held in RA<
//NOTE: no yielding in this iteration.
bool for_each(const closure &);
// Get whether a user enabled typing events for a room. The type string
// can be "send" or "sync" prevent typing one's events from being sent or
// others' from being sync'ed, respectively
bool allow(const id::user &, const id::room &, const string_view &type);
} }
/// Interface to update the typing state, generate all events, send etc.
struct ircd::m::typing::commit
{
commit(const edu &);
};
struct ircd::m::edu::m_typing struct ircd::m::edu::m_typing
:json::tuple :json::tuple
< <
@ -29,27 +48,15 @@ struct ircd::m::edu::m_typing
using super_type::operator=; using super_type::operator=;
}; };
struct ircd::m::typing struct ircd::m::typing::typist
:m::edu::m_typing
{ {
struct commit; using is_transparent = void;
using closure = std::function<bool (const typing &)>; system_point timesout;
m::user::id::buf user_id;
m::room::id::buf room_id;
// Iterate all of the active typists held in RA< bool operator()(const typist &a, const string_view &b) const noexcept;
//NOTE: no yielding in this iteration. bool operator()(const string_view &a, const typist &b) const noexcept;
static bool for_each(const closure &); bool operator()(const typist &a, const typist &b) const noexcept;
// Get whether a user enabled typing events for a room. The type string
// can be "send" or "sync" prevent typing one's events from being sent or
// others' from being sync'ed, respectively
static bool allow(const id::user &, const id::room &, const string_view &type);
using edu::m_typing::m_typing;
};
/// Interface to update the typing state, generate all events, send etc.
struct ircd::m::typing::commit
{
commit(const typing &);
}; };

View file

@ -8,77 +8,82 @@
// copyright notice and this permission notice is present in all copies. The // copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file. // full license for this software is available in the LICENSE file.
using namespace ircd; namespace ircd::m::typing
{
static system_point calc_timesout(milliseconds relative);
static bool update_state(const edu &);
static m::event::id::buf set_typing(const edu &);
static void _handle_edu(const m::event &, const edu &);
static void handle_edu(const m::event &, m::vm::eval &);
static void timeout_timeout(const typist &);
static void timeout_check();
static void timeout_worker();
log::log extern log::log log;
typing_log extern ctx::dock dock;
extern ctx::mutex mutex;
extern std::set<typist, typist> typists;
extern conf::item<milliseconds> timeout_max;
extern conf::item<milliseconds> timeout_min;
extern conf::item<milliseconds> timeout_int;
extern hookfn<vm::eval &> on_eval;
extern context timeout_context;
}
decltype(ircd::m::typing::log)
ircd::m::typing::log
{ {
"m.typing" "m.typing"
}; };
struct typist decltype(ircd::m::typing::dock)
{ ircd::m::typing::dock;
using is_transparent = void;
system_point timesout; decltype(ircd::m::typing::mutex)
m::user::id::buf user_id; ircd::m::typing::mutex;
m::room::id::buf room_id;
bool operator()(const typist &a, const string_view &b) const; decltype(ircd::m::typing::typists)
bool operator()(const string_view &a, const typist &b) const; ircd::m::typing::typists;
bool operator()(const typist &a, const typist &b) const;
};
static ctx::dock decltype(ircd::m::typing::timeout_max)
timeout_dock; ircd::m::typing::timeout_max
static ctx::mutex
typists_mutex;
static std::set<typist, typist>
typists;
conf::item<milliseconds>
timeout_max
{ {
{ "name", "ircd.typing.timeout.max" }, { "name", "ircd.typing.timeout.max" },
{ "default", 90 * 1000L }, { "default", 90 * 1000L },
}; };
conf::item<milliseconds> decltype(ircd::m::typing::timeout_min)
timeout_min ircd::m::typing::timeout_min
{ {
{ "name", "ircd.typing.timeout.min" }, { "name", "ircd.typing.timeout.min" },
{ "default", 15 * 1000L }, { "default", 15 * 1000L },
}; };
conf::item<milliseconds> decltype(ircd::m::typing::timeout_int)
timeout_int ircd::m::typing::timeout_int
{ {
{ "name", "ircd.typing.timeout.int" }, { "name", "ircd.typing.timeout.int" },
{ "default", 5 * 1000L }, { "default", 5 * 1000L },
}; };
static system_point calc_timesout(milliseconds relative); decltype(ircd::m::typing::timeout_context)
static bool update_state(const m::edu::m_typing &); ircd::m::typing::timeout_context
{
// "typing",
// typing edu handler stack (local and remote) 768_KiB,
// context::POST,
timeout_worker,
static m::event::id::buf set_typing(const m::edu::m_typing &edu); };
static void _handle_edu_m_typing(const m::event &, const m::typing &edu);
static void handle_edu_m_typing(const m::event &, m::vm::eval &);
/// Hooks all federation typing edus from remote servers as well as /// Hooks all federation typing edus from remote servers as well as
/// the above commit from local clients. This hook rewrites the edu into /// the above commit from local clients. This hook rewrites the edu into
/// a new event formatted for client /sync and then runs that through eval /// a new event formatted for client /sync and then runs that through eval
/// so our clients can receive the typing events. /// so our clients can receive the typing events.
/// ///
m::hookfn<m::vm::eval &> decltype(ircd::m::typing::on_eval)
_m_typing_eval ircd::m::typing::on_eval
{ {
handle_edu_m_typing, handle_edu,
{ {
{ "_site", "vm.eval" }, { "_site", "vm.eval" },
{ "type", "m.typing" }, { "type", "m.typing" },
@ -86,7 +91,104 @@ _m_typing_eval
}; };
void void
handle_edu_m_typing(const m::event &event, ircd::m::typing::timeout_worker()
try
{
for(;; ctx::sleep(milliseconds(timeout_int)))
{
dock.wait([]
{
return !typists.empty();
});
const std::lock_guard lock
{
mutex
};
timeout_check();
}
}
catch(const std::exception &e)
{
log::critical
{
log, "Typing timeout worker fatal :%s",
e.what()
};
}
void
ircd::m::typing::timeout_check()
try
{
const auto now
{
ircd::now<system_point>()
};
for(auto it(begin(typists)); it != end(typists); )
{
if(it->timesout < now)
{
// have to restart the loop if there's a timeout because
// the call will have yields and invalidate iterators etc.
timeout_timeout(*it);
it = typists.erase(it);
}
else ++it;
}
}
catch(const std::exception &e)
{
log::critical
{
log, "Typing timeout check :%s",
e.what(),
};
}
void
ircd::m::typing::timeout_timeout(const typist &t)
try
{
assert(run::level == run::level::RUN);
const edu edu
{
{ "user_id", t.user_id },
{ "room_id", t.room_id },
{ "typing", false },
};
log::debug
{
log, "Typing timeout for %s in %s",
string_view{t.user_id},
string_view{t.room_id}
};
m::event event;
json::get<"origin"_>(event) = my_host();
json::get<"type"_>(event) = "m.typing"_sv;
// Call this manually because it currently composes the event
// sent to clients to stop the typing for this timed out user.
_handle_edu(event, edu);
}
catch(const std::exception &e)
{
log::error
{
log, "Typing timeout for %s in %s :%s",
string_view{t.user_id},
string_view{t.room_id},
e.what(),
};
}
void
ircd::m::typing::handle_edu(const m::event &event,
m::vm::eval &eval) m::vm::eval &eval)
try try
{ {
@ -95,7 +197,7 @@ try
at<"content"_>(event) at<"content"_>(event)
}; };
_handle_edu_m_typing(event, content); _handle_edu(event, content);
} }
catch(const ctx::interrupted &) catch(const ctx::interrupted &)
{ {
@ -105,7 +207,7 @@ catch(const std::exception &e)
{ {
log::derror log::derror
{ {
typing_log, "m.typing from %s :%s", log, "m.typing from %s :%s",
json::get<"origin"_>(event), json::get<"origin"_>(event),
e.what(), e.what(),
}; };
@ -114,8 +216,8 @@ catch(const std::exception &e)
} }
void void
_handle_edu_m_typing(const m::event &event, ircd::m::typing::_handle_edu(const m::event &event,
const m::typing &edu) const edu &edu)
{ {
// This check prevents interference between the two competing edu formats; // This check prevents interference between the two competing edu formats;
// The federation edu has a room_id field while the client edu only has a // The federation edu has a room_id field while the client edu only has a
@ -140,12 +242,11 @@ _handle_edu_m_typing(const m::event &event,
// Check if this server can send an edu for this user. We make an exception // Check if this server can send an edu for this user. We make an exception
// for our server to allow the timeout worker to use this codepath. // for our server to allow the timeout worker to use this codepath.
if(!my_host(origin)) if(!my_host(origin) && user_id.host() != origin)
if(user_id.host() != origin)
{ {
log::dwarning log::dwarning
{ {
typing_log, "Ignoring %s from %s for alien %s", log, "Ignoring %s from %s for alien %s",
at<"type"_>(event), at<"type"_>(event),
origin, origin,
string_view{user_id} string_view{user_id}
@ -160,7 +261,7 @@ _handle_edu_m_typing(const m::event &event,
{ {
log::dwarning log::dwarning
{ {
typing_log, "Ignoring %s from '%s' in %s :denied by m.room.server_acl.", log, "Ignoring %s from '%s' in %s :denied by m.room.server_acl.",
at<"type"_>(event), at<"type"_>(event),
origin, origin,
string_view{room_id}, string_view{room_id},
@ -182,7 +283,7 @@ _handle_edu_m_typing(const m::event &event,
{ {
log::dwarning log::dwarning
{ {
typing_log, "Ignoring %s from %s for user %s because not in room '%s'", log, "Ignoring %s from %s for user %s because not in room '%s'",
at<"type"_>(event), at<"type"_>(event),
origin, origin,
string_view{user_id}, string_view{user_id},
@ -205,9 +306,155 @@ _handle_edu_m_typing(const m::event &event,
set_typing(edu); set_typing(edu);
} }
// ircd::m::event::id::buf
// interface ircd::m::typing::set_typing(const edu &edu)
// {
assert(json::get<"room_id"_>(edu));
const m::user::id &user_id
{
at<"user_id"_>(edu)
};
const m::user user
{
user_id
};
if(!exists(user))
create(user);
const m::user::room user_room
{
user
};
const auto &timeout
{
json::get<"timeout"_>(edu)?
json::get<"timeout"_>(edu):
json::get<"typing"_>(edu)?
milliseconds(timeout_max).count():
0L
};
const auto evid
{
send(user_room, user_id, "ircd.typing",
{
{ "room_id", at<"room_id"_>(edu) },
{ "typing", json::get<"typing"_>(edu) },
{ "timeout", timeout },
})
};
char pbuf[32];
log::info
{
log, "%s %s typing in %s timeout:%s",
string_view{user_id},
json::get<"typing"_>(edu)?
"started"_sv:
"stopped"_sv,
at<"room_id"_>(edu),
util::pretty(pbuf, milliseconds(timeout), 1),
};
return evid;
}
bool
ircd::m::typing::update_state(const edu &object)
try
{
const auto &user_id
{
at<"user_id"_>(object)
};
const auto &room_id
{
at<"room_id"_>(object)
};
const auto &typing
{
at<"typing"_>(object)
};
const milliseconds timeout
{
at<"timeout"_>(object)
};
const std::lock_guard lock
{
mutex
};
auto it
{
typists.lower_bound(user_id)
};
const bool was_typing
{
it != end(typists) && it->user_id == user_id
};
if(typing && !was_typing)
{
typists.emplace_hint(it, typist
{
calc_timesout(timeout), user_id, room_id
});
dock.notify_one();
}
else if(typing && was_typing)
{
auto &t(const_cast<typist &>(*it));
t.timesout = calc_timesout(timeout);
}
else if(!typing && was_typing)
{
typists.erase(it);
}
const bool transmit
{
(typing && !was_typing) || (!typing && was_typing)
};
log::debug
{
log, "Typing %s in %s now[%b] was[%b] xmit[%b]",
string_view{at<"user_id"_>(object)},
string_view{at<"room_id"_>(object)},
json::get<"typing"_>(object),
was_typing,
transmit
};
return transmit;
}
catch(const std::exception &e)
{
log::error
{
log, "Failed to update state :%s",
e.what(),
};
throw;
}
ircd::system_point
ircd::m::typing::calc_timesout(milliseconds timeout)
{
timeout = std::max(timeout, milliseconds(timeout_min));
timeout = std::min(timeout, milliseconds(timeout_max));
return now<system_point>() + timeout;
}
/// typing commit handler stack (local user) /// typing commit handler stack (local user)
/// ///
@ -218,7 +465,7 @@ _handle_edu_m_typing(const m::event &event,
/// event to clients we hook it during eval and create a new event formatted /// event to clients we hook it during eval and create a new event formatted
/// for clients and then run that through eval too. (see below). /// for clients and then run that through eval too. (see below).
/// ///
ircd::m::typing::commit::commit(const m::typing &edu) ircd::m::typing::commit::commit(const edu &edu)
{ {
using json::at; using json::at;
@ -306,7 +553,7 @@ ircd::m::typing::for_each(const closure &closure)
{ {
const std::lock_guard lock const std::lock_guard lock
{ {
typists_mutex mutex
}; };
for(const auto &t : typists) for(const auto &t : typists)
@ -316,7 +563,7 @@ ircd::m::typing::for_each(const closure &closure)
system_clock::to_time_t(t.timesout) system_clock::to_time_t(t.timesout)
}; };
const m::typing event const edu event
{ {
{ "user_id", t.user_id }, { "user_id", t.user_id },
{ "room_id", t.room_id }, { "room_id", t.room_id },
@ -331,304 +578,30 @@ ircd::m::typing::for_each(const closure &closure)
return true; return true;
} }
//
// timeout worker stack
//
static void timeout_timeout(const typist &);
static void timeout_check();
static void timeout_worker();
static context
timeout_context
{
"typing",
256_KiB,
context::POST,
timeout_worker
};
static const ircd::run::changed
timeout_context_terminate
{
run::level::QUIT, []
{
timeout_context.terminate();
}
};
void
timeout_worker()
try
{
for(;; ctx::sleep(milliseconds(timeout_int)))
{
timeout_dock.wait([]
{
return !typists.empty();
});
timeout_check();
}
}
catch(const std::exception &e)
{
log::critical
{
typing_log, "Typing timeout worker fatal :%s",
e.what()
};
}
void
timeout_check()
try
{
const std::lock_guard lock
{
typists_mutex
};
const auto now
{
ircd::now<system_point>()
};
for(auto it(begin(typists)); it != end(typists); )
{
if(it->timesout < now)
{
// have to restart the loop if there's a timeout because
// the call will have yields and invalidate iterators etc.
timeout_timeout(*it);
it = typists.erase(it);
}
else ++it;
}
}
catch(const std::exception &e)
{
log::critical
{
typing_log, "Typing timeout check :%s",
e.what(),
};
}
void
timeout_timeout(const typist &t)
try
{
assert(run::level == run::level::RUN);
const m::typing edu
{
{ "user_id", t.user_id },
{ "room_id", t.room_id },
{ "typing", false },
};
log::debug
{
typing_log, "Typing timeout for %s in %s",
string_view{t.user_id},
string_view{t.room_id}
};
m::event event;
json::get<"origin"_>(event) = my_host();
json::get<"type"_>(event) = "m.typing"_sv;
// Call this manually because it currently composes the event
// sent to clients to stop the typing for this timed out user.
_handle_edu_m_typing(event, edu);
}
catch(const std::exception &e)
{
log::error
{
typing_log, "Typing timeout for %s in %s :%s",
string_view{t.user_id},
string_view{t.room_id},
e.what(),
};
}
//
// internal
//
ircd::m::event::id::buf
set_typing(const m::edu::m_typing &edu)
{
assert(json::get<"room_id"_>(edu));
const m::user::id &user_id
{
at<"user_id"_>(edu)
};
const m::user user
{
user_id
};
if(!exists(user))
create(user);
const m::user::room user_room
{
user
};
const auto &timeout
{
json::get<"timeout"_>(edu)?
json::get<"timeout"_>(edu):
json::get<"typing"_>(edu)?
milliseconds(timeout_max).count():
0L
};
const auto evid
{
send(user_room, user_id, "ircd.typing",
{
{ "room_id", at<"room_id"_>(edu) },
{ "typing", json::get<"typing"_>(edu) },
{ "timeout", timeout },
})
};
char pbuf[32];
log::info
{
typing_log, "%s %s typing in %s timeout:%s",
string_view{user_id},
json::get<"typing"_>(edu)?
"started"_sv:
"stopped"_sv,
at<"room_id"_>(edu),
pretty(pbuf, milliseconds(long(timeout)), 1),
};
return evid;
}
bool
update_state(const m::edu::m_typing &object)
try
{
const auto &user_id
{
at<"user_id"_>(object)
};
const auto &room_id
{
at<"room_id"_>(object)
};
const auto &typing
{
at<"typing"_>(object)
};
const milliseconds timeout
{
at<"timeout"_>(object)
};
const std::lock_guard lock
{
typists_mutex
};
auto it
{
typists.lower_bound(user_id)
};
const bool was_typing
{
it != end(typists) && it->user_id == user_id
};
if(typing && !was_typing)
{
typists.emplace_hint(it, typist
{
calc_timesout(timeout), user_id, room_id
});
timeout_dock.notify_one();
}
else if(typing && was_typing)
{
auto &t(const_cast<typist &>(*it));
t.timesout = calc_timesout(timeout);
}
else if(!typing && was_typing)
{
typists.erase(it);
}
const bool transmit
{
(typing && !was_typing) || (!typing && was_typing)
};
log::debug
{
typing_log, "Typing %s in %s now[%b] was[%b] xmit[%b]",
string_view{at<"user_id"_>(object)},
string_view{at<"room_id"_>(object)},
json::get<"typing"_>(object),
was_typing,
transmit
};
return transmit;
}
catch(const std::exception &e)
{
log::error
{
typing_log, "Failed to update state :%s",
e.what(),
};
throw;
}
system_point
calc_timesout(milliseconds timeout)
{
timeout = std::max(timeout, milliseconds(timeout_min));
timeout = std::min(timeout, milliseconds(timeout_max));
return now<system_point>() + timeout;
}
// //
// typist struct // typist struct
// //
bool bool
typist::operator()(const typist &a, const string_view &b) ircd::m::typing::typist::operator()(const typist &a,
const const string_view &b)
const noexcept
{ {
return a.user_id < b; return a.user_id < b;
} }
bool bool
typist::operator()(const string_view &a, const typist &b) ircd::m::typing::typist::operator()(const string_view &a,
const const typist &b)
const noexcept
{ {
return a < b.user_id; return a < b.user_id;
} }
bool bool
typist::operator()(const typist &a, const typist &b) ircd::m::typing::typist::operator()(const typist &a,
const const typist &b)
const noexcept
{ {
return a.user_id < b.user_id; return a.user_id < b.user_id;
} }

View file

@ -51,7 +51,7 @@ put__typing(client &client,
request.get("timeout", milliseconds(timeout_default).count()) request.get("timeout", milliseconds(timeout_default).count())
}; };
const m::typing event const m::typing::edu event
{ {
{ "room_id", room_id }, { "room_id", room_id },
{ "typing", typing }, { "typing", typing },

View file

@ -12547,7 +12547,7 @@ bool
console_cmd__user__typing(opt &out, const string_view &line) console_cmd__user__typing(opt &out, const string_view &line)
{ {
m::typing::for_each([&out] m::typing::for_each([&out]
(const m::typing &event) (const m::typing::edu &event)
{ {
out << event << std::endl; out << event << std::endl;
return true; return true;