degesch: export input and IRC hooks
This commit is contained in:
parent
c912726f49
commit
59a4c356dd
479
degesch.c
479
degesch.c
|
@ -7280,6 +7280,11 @@ irc_hook_insert (struct app_context *ctx, struct irc_hook *hook)
|
|||
|
||||
// --- Lua ---------------------------------------------------------------------
|
||||
|
||||
// Each plugin has its own Lua state object, so that a/ they don't disturb each
|
||||
// other and b/ unloading a plugin releases all resources.
|
||||
//
|
||||
// References to internal objects (buffers, servers) are all weak.
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
|
||||
struct lua_plugin
|
||||
|
@ -7303,6 +7308,427 @@ struct plugin_vtable lua_plugin_vtable =
|
|||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// The registry can be used as a cache for weakly referenced objects
|
||||
|
||||
static bool
|
||||
lua_cache_get (lua_State *L, void *object)
|
||||
{
|
||||
lua_rawgetp (L, LUA_REGISTRYINDEX, object);
|
||||
if (lua_isnil (L, -1))
|
||||
{
|
||||
lua_pop (L, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
lua_cache_store (lua_State *L, void *object, int index)
|
||||
{
|
||||
lua_pushvalue (L, index);
|
||||
lua_rawsetp (L, LUA_REGISTRYINDEX, object);
|
||||
}
|
||||
|
||||
static void
|
||||
lua_cache_invalidate (lua_State *L, void *object)
|
||||
{
|
||||
lua_pushnil (L);
|
||||
lua_rawsetp (L, LUA_REGISTRYINDEX, object);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for the Lua metatable
|
||||
|
||||
struct lua_buffer
|
||||
{
|
||||
struct lua_plugin *plugin; ///< The plugin we belong to
|
||||
struct buffer *buffer; ///< The buffer
|
||||
struct weak_ref_link *weak_ref; ///< A weak reference link
|
||||
};
|
||||
|
||||
static int
|
||||
lua_buffer_gc (lua_State *L)
|
||||
{
|
||||
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
|
||||
if (wrapper->buffer)
|
||||
{
|
||||
lua_cache_invalidate (L, wrapper->buffer);
|
||||
buffer_weak_unref (wrapper->buffer, &wrapper->weak_ref);
|
||||
wrapper->buffer = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_buffer_log (lua_State *L)
|
||||
{
|
||||
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
|
||||
luaL_argcheck (L, wrapper->buffer, 1, "dead reference used");
|
||||
const char *message = luaL_checkstring (L, 2);
|
||||
|
||||
struct buffer *buffer = wrapper->buffer;
|
||||
log_full (wrapper->plugin->ctx, buffer->server, buffer,
|
||||
BUFFER_LINE_STATUS, "#s", message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_buffer_table[] =
|
||||
{
|
||||
// TODO: some useful methods or values
|
||||
{ "__gc", lua_buffer_gc },
|
||||
{ "log", lua_buffer_log },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
lua_buffer_invalidate (void *object, void *user_data)
|
||||
{
|
||||
struct lua_buffer *wrapper = user_data;
|
||||
wrapper->buffer = NULL;
|
||||
wrapper->weak_ref = NULL;
|
||||
// This can in theory call the GC, order isn't arbitrary here
|
||||
lua_cache_invalidate (wrapper->plugin->L, object);
|
||||
}
|
||||
|
||||
static void
|
||||
lua_plugin_push_buffer (struct lua_plugin *plugin, struct buffer *buffer)
|
||||
{
|
||||
struct lua_State *L = plugin->L;
|
||||
if (lua_cache_get (L, buffer))
|
||||
return;
|
||||
|
||||
struct lua_buffer *wrapper = lua_newuserdata (L, sizeof *wrapper);
|
||||
luaL_setmetatable (L, XLUA_BUFFER_METATABLE);
|
||||
wrapper->plugin = plugin;
|
||||
wrapper->buffer = buffer;
|
||||
wrapper->weak_ref = buffer_weak_ref
|
||||
(buffer, lua_buffer_invalidate, wrapper);
|
||||
lua_cache_store (L, buffer, -1);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define XLUA_SERVER_METATABLE "server" ///< Identifier for the Lua metatable
|
||||
|
||||
struct lua_server
|
||||
{
|
||||
struct lua_plugin *plugin; ///< The plugin we belong to
|
||||
struct server *server; ///< The server
|
||||
struct weak_ref_link *weak_ref; ///< A weak reference link
|
||||
};
|
||||
|
||||
static int
|
||||
lua_server_gc (lua_State *L)
|
||||
{
|
||||
struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
|
||||
if (wrapper->server)
|
||||
{
|
||||
lua_cache_invalidate (L, wrapper->server);
|
||||
server_weak_unref (wrapper->server, &wrapper->weak_ref);
|
||||
wrapper->server = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_server_send (lua_State *L)
|
||||
{
|
||||
struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
|
||||
luaL_argcheck (L, wrapper->server, 1, "dead reference used");
|
||||
const char *line = luaL_checkstring (L, 2);
|
||||
|
||||
irc_send (wrapper->server, "%s", line);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_server_table[] =
|
||||
{
|
||||
// TODO: some useful methods or values
|
||||
{ "__gc", lua_server_gc },
|
||||
{ "send", lua_server_send },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
lua_server_invalidate (void *object, void *user_data)
|
||||
{
|
||||
struct lua_server *wrapper = user_data;
|
||||
wrapper->server = NULL;
|
||||
wrapper->weak_ref = NULL;
|
||||
// This can in theory call the GC, order isn't arbitrary here
|
||||
lua_cache_invalidate (wrapper->plugin->L, object);
|
||||
}
|
||||
|
||||
static void
|
||||
lua_plugin_push_server (struct lua_plugin *plugin, struct server *server)
|
||||
{
|
||||
struct lua_State *L = plugin->L;
|
||||
if (lua_cache_get (L, server))
|
||||
return;
|
||||
|
||||
struct lua_server *wrapper = lua_newuserdata (L, sizeof *wrapper);
|
||||
luaL_setmetatable (L, XLUA_SERVER_METATABLE);
|
||||
wrapper->plugin = plugin;
|
||||
wrapper->server = server;
|
||||
wrapper->weak_ref = server_weak_ref
|
||||
(server, lua_server_invalidate, wrapper);
|
||||
lua_cache_store (L, server, -1);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define XLUA_HOOK_METATABLE "hook" ///< Identifier for the Lua metatable
|
||||
|
||||
enum lua_hook_type
|
||||
{
|
||||
XLUA_HOOK_DEFUNCT, ///< No longer functional
|
||||
XLUA_HOOK_INPUT, ///< Input hook
|
||||
XLUA_HOOK_IRC, ///< IRC hook
|
||||
};
|
||||
|
||||
struct lua_hook
|
||||
{
|
||||
struct lua_plugin *plugin; ///< The plugin we belong to
|
||||
enum lua_hook_type type; ///< Type of the hook
|
||||
union
|
||||
{
|
||||
struct input_hook input_hook; ///< Input hook
|
||||
struct irc_hook irc_hook; ///< IRC hook
|
||||
}
|
||||
data; ///< Hook data
|
||||
};
|
||||
|
||||
static int
|
||||
lua_hook_unhook (lua_State *L)
|
||||
{
|
||||
struct lua_hook *hook = luaL_checkudata (L, 1, XLUA_HOOK_METATABLE);
|
||||
switch (hook->type)
|
||||
{
|
||||
case XLUA_HOOK_INPUT:
|
||||
LIST_UNLINK (hook->plugin->ctx->input_hooks, &hook->data.input_hook);
|
||||
break;
|
||||
case XLUA_HOOK_IRC:
|
||||
LIST_UNLINK (hook->plugin->ctx->irc_hooks, &hook->data.irc_hook);
|
||||
break;
|
||||
default:
|
||||
hard_assert (!"invalid hook type");
|
||||
case XLUA_HOOK_DEFUNCT:
|
||||
break;
|
||||
}
|
||||
|
||||
// The hook no longer has to stay alive
|
||||
lua_pushnil (L);
|
||||
lua_rawsetp (L, LUA_REGISTRYINDEX, hook);
|
||||
hook->type = XLUA_HOOK_DEFUNCT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The hook dies either when the plugin requests it or at plugin unload
|
||||
static luaL_Reg lua_hook_table[] =
|
||||
{
|
||||
{ "unhook", lua_hook_unhook },
|
||||
{ "__gc", lua_hook_unhook },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// Append a traceback to all errors so that we can later extract it
|
||||
static int
|
||||
lua_plugin_error_handler (lua_State *L)
|
||||
{
|
||||
luaL_traceback (L, L, luaL_checkstring (L, 1), 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
lua_plugin_process_error (struct lua_plugin *self, const char *message,
|
||||
struct error **e)
|
||||
{
|
||||
struct str_vector v;
|
||||
str_vector_init (&v);
|
||||
cstr_split_ignore_empty (message, '\n', &v);
|
||||
|
||||
if (v.len < 2)
|
||||
error_set (e, "%s", message);
|
||||
else
|
||||
{
|
||||
error_set (e, "%s", v.vector[0]);
|
||||
log_global_debug (self->ctx, "Lua: plugin \"#s\": #s",
|
||||
self->super.name, v.vector[1]);
|
||||
for (size_t i = 2; i < v.len; i++)
|
||||
log_global_debug (self->ctx, " #s", v.vector[i]);
|
||||
}
|
||||
|
||||
str_vector_free (&v);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convenience function; replaces the "original" string or produces an error
|
||||
static bool
|
||||
lua_plugin_handle_string_filter_result (struct lua_plugin *self,
|
||||
int result, char **original, bool utf8, struct error **e)
|
||||
{
|
||||
lua_State *L = self->L;
|
||||
if (result)
|
||||
return lua_plugin_process_error (self, lua_tostring (L, -1), e);
|
||||
if (lua_isnil (L, -1))
|
||||
return NULL;
|
||||
if (!lua_isstring (L, -1))
|
||||
FAIL ("must return either a string or nil");
|
||||
|
||||
size_t len;
|
||||
const char *processed = lua_tolstring (L, -1, &len);
|
||||
if (utf8 && !utf8_validate (processed, len))
|
||||
FAIL ("must return valid UTF-8");
|
||||
|
||||
// Only replace the string if it's different
|
||||
if (strcmp (processed, *original))
|
||||
{
|
||||
free (*original);
|
||||
*original = xstrdup (processed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static char *
|
||||
lua_input_hook_filter (struct input_hook *self, struct buffer *buffer,
|
||||
char *input)
|
||||
{
|
||||
struct lua_hook *hook = (struct lua_hook *)
|
||||
((char *) self - offsetof (struct lua_hook, data.input_hook));
|
||||
struct lua_plugin *plugin = hook->plugin;
|
||||
lua_State *L = plugin->L;
|
||||
|
||||
lua_pushcfunction (L, lua_plugin_error_handler);
|
||||
|
||||
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
|
||||
lua_getuservalue (L, -1); // Retrieve function
|
||||
lua_insert (L, -2); // Swap with thet hook
|
||||
|
||||
lua_plugin_push_buffer (plugin, buffer); // 2: buffer
|
||||
lua_pushstring (L, input); // 3: input
|
||||
|
||||
struct error *e = NULL;
|
||||
bool failed = !lua_plugin_handle_string_filter_result
|
||||
(plugin, lua_pcall (L, 3, 1, -5), &input, true, &e);
|
||||
lua_pop (L, 1);
|
||||
|
||||
if (failed)
|
||||
{
|
||||
log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s: #s",
|
||||
plugin->super.name, "input hook", e->message);
|
||||
error_free (e);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
struct input_hook_vtable lua_input_hook_vtable =
|
||||
{
|
||||
.filter = lua_input_hook_filter,
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static char *
|
||||
lua_irc_hook_filter (struct irc_hook *self, struct server *s, char *message)
|
||||
{
|
||||
struct lua_hook *hook = (struct lua_hook *)
|
||||
((char *) self - offsetof (struct lua_hook, data.irc_hook));
|
||||
struct lua_plugin *plugin = hook->plugin;
|
||||
lua_State *L = plugin->L;
|
||||
|
||||
lua_pushcfunction (L, lua_plugin_error_handler);
|
||||
|
||||
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
|
||||
lua_getuservalue (L, -1); // Retrieve function
|
||||
lua_insert (L, -2); // Swap with thet hook
|
||||
|
||||
lua_plugin_push_server (plugin, s); // 2: server
|
||||
lua_pushstring (L, message); // 3: message
|
||||
|
||||
struct error *e = NULL;
|
||||
bool failed = !lua_plugin_handle_string_filter_result
|
||||
(plugin, lua_pcall (L, 3, 1, -5), &message, false, &e);
|
||||
lua_pop (L, 1);
|
||||
|
||||
if (failed)
|
||||
{
|
||||
log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s: #s",
|
||||
plugin->super.name, "IRC hook", e->message);
|
||||
error_free (e);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
struct irc_hook_vtable lua_irc_hook_vtable =
|
||||
{
|
||||
.filter = lua_irc_hook_filter,
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static struct lua_hook *
|
||||
lua_plugin_push_hook
|
||||
(struct lua_plugin *plugin, int callback_index, enum lua_hook_type type)
|
||||
{
|
||||
struct lua_State *L = plugin->L;
|
||||
struct lua_hook *hook = lua_newuserdata (L, sizeof *hook);
|
||||
luaL_setmetatable (L, XLUA_HOOK_METATABLE);
|
||||
memset (hook, 0, sizeof *hook);
|
||||
hook->type = type;
|
||||
hook->plugin = plugin;
|
||||
|
||||
// Associate the callback with the hook
|
||||
lua_pushvalue (L, callback_index);
|
||||
lua_setuservalue (L, -2);
|
||||
|
||||
// Make sure the hook doesn't get garbage collected and return it
|
||||
lua_pushvalue (L, -1);
|
||||
lua_rawsetp (L, LUA_REGISTRYINDEX, hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_plugin_hook_input (lua_State *L)
|
||||
{
|
||||
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
|
||||
luaL_checktype (L, 1, LUA_TFUNCTION);
|
||||
lua_Integer priority = luaL_optinteger (L, 2, 0);
|
||||
|
||||
struct lua_hook *hook = lua_plugin_push_hook (plugin, 1, XLUA_HOOK_INPUT);
|
||||
hook->data.input_hook.vtable = &lua_input_hook_vtable;
|
||||
hook->data.input_hook.priority = priority;
|
||||
input_hook_insert (plugin->ctx, &hook->data.input_hook);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_plugin_hook_irc (lua_State *L)
|
||||
{
|
||||
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
|
||||
luaL_checktype (L, 1, LUA_TFUNCTION);
|
||||
lua_Integer priority = luaL_optinteger (L, 2, 0);
|
||||
|
||||
struct lua_hook *hook = lua_plugin_push_hook (plugin, 1, XLUA_HOOK_IRC);
|
||||
hook->data.irc_hook.vtable = &lua_irc_hook_vtable;
|
||||
hook->data.irc_hook.priority = priority;
|
||||
irc_hook_insert (plugin->ctx, &hook->data.irc_hook);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_plugin_library[] =
|
||||
{
|
||||
{ "hook_input", lua_plugin_hook_input },
|
||||
{ "hook_irc", lua_plugin_hook_irc },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void *
|
||||
lua_plugin_alloc (void *ud, void *ptr, size_t o_size, size_t n_size)
|
||||
{
|
||||
|
@ -7326,10 +7752,22 @@ lua_plugin_panic (lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_plugin_library[] =
|
||||
static void
|
||||
lua_plugin_create_meta (lua_State *L, const char *name, luaL_Reg *fns)
|
||||
{
|
||||
{ NULL, NULL },
|
||||
};
|
||||
luaL_newmetatable (L, name);
|
||||
luaL_setfuncs (L, fns, 0);
|
||||
|
||||
// Otherwise any non-meta functions would be inaccessible
|
||||
bool has_own_index = (lua_getfield (L, -1, "__index") != LUA_TNIL);
|
||||
lua_pop (L, 1);
|
||||
if (!has_own_index)
|
||||
{
|
||||
lua_pushvalue (L, -1);
|
||||
lua_setfield (L, -2, "__index");
|
||||
}
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
static struct plugin *
|
||||
lua_plugin_load (struct app_context *ctx, const char *filename,
|
||||
|
@ -7345,24 +7783,33 @@ lua_plugin_load (struct app_context *ctx, const char *filename,
|
|||
lua_atpanic (L, lua_plugin_panic);
|
||||
luaL_openlibs (L);
|
||||
|
||||
luaL_newlib (L, lua_plugin_library);
|
||||
lua_setglobal (L, PROGRAM_NAME);
|
||||
|
||||
int ret;
|
||||
if ((ret = luaL_loadfile (L, filename))
|
||||
|| (ret = lua_pcall (L, 0, 0, 0)))
|
||||
{
|
||||
error_set (e, "%s: %s", "Lua", lua_tostring (L, -1));
|
||||
lua_close (L);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct lua_plugin *plugin = xcalloc (1, sizeof *plugin);
|
||||
plugin->super.name = NULL;
|
||||
plugin->super.vtable = &lua_plugin_vtable;
|
||||
plugin->ctx = ctx;
|
||||
plugin->L = L;
|
||||
return &plugin->super;
|
||||
|
||||
// Register the degesch library with "plugin" as an upvalue
|
||||
luaL_checkversion (L);
|
||||
luaL_newlibtable (L, lua_plugin_library);
|
||||
lua_pushlightuserdata (L, plugin);
|
||||
luaL_setfuncs (L, lua_plugin_library, 1);
|
||||
lua_setglobal (L, PROGRAM_NAME);
|
||||
|
||||
// Create metatables for our objects
|
||||
lua_plugin_create_meta (L, XLUA_HOOK_METATABLE, lua_hook_table);
|
||||
lua_plugin_create_meta (L, XLUA_BUFFER_METATABLE, lua_buffer_table);
|
||||
lua_plugin_create_meta (L, XLUA_SERVER_METATABLE, lua_server_table);
|
||||
|
||||
int ret;
|
||||
if (!(ret = luaL_loadfile (L, filename))
|
||||
&& !(ret = lua_pcall (L, 0, 0, 0)))
|
||||
return &plugin->super;
|
||||
|
||||
error_set (e, "%s: %s", "Lua", lua_tostring (L, -1));
|
||||
lua_close (L);
|
||||
free (plugin);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif // HAVE_LUA
|
||||
|
|
Loading…
Reference in New Issue