diff --git a/degesch.c b/degesch.c index a761dee..641629c 100644 --- a/degesch.c +++ b/degesch.c @@ -9580,149 +9580,6 @@ lua_plugin_push_connection (struct lua_plugin *plugin, int socket_fd) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -/// Identifier for the Lua metatable -#define XLUA_CONNECTOR_METATABLE "connector" - -struct lua_connector -{ - struct lua_plugin *plugin; ///< The plugin we belong to - struct connector connector; ///< Connector object - bool active; ///< Whether the connector is alive - - int ref_on_success; ///< Reference to "on_success" callback - int ref_on_error; ///< Reference to "on_error" callback - - char *last_error; ///< Connecting error, if any -}; - -static void -lua_connector_discard (struct lua_connector *self) -{ - if (self->active) - { - connector_free (&self->connector); - self->active = false; - - luaL_unref (self->plugin->L, LUA_REGISTRYINDEX, self->ref_on_success); - luaL_unref (self->plugin->L, LUA_REGISTRYINDEX, self->ref_on_error); - self->ref_on_success = LUA_REFNIL; - self->ref_on_error = LUA_REFNIL; - } - - free (self->last_error); - self->last_error = NULL; - - lua_cache_invalidate (self->plugin->L, self); -} - -static int -lua_connector_abort (lua_State *L) -{ - lua_connector_discard (luaL_checkudata (L, 1, XLUA_CONNECTOR_METATABLE)); - return 0; -} - -static luaL_Reg lua_connector_table[] = -{ - { "abort", lua_connector_abort }, - { "__gc", lua_connector_abort }, - { NULL, NULL } -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -lua_connector_on_connected (void *user_data, int socket, const char *hostname) -{ - struct lua_connector *self = user_data; - - // TODO: use the hostname for SNI once TLS is implemented - - if (self->ref_on_success != LUA_REFNIL) - { - lua_State *L = self->plugin->L; - lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_on_success); - struct lua_connection *connection = - lua_plugin_push_connection (self->plugin, socket); // 1: connection - lua_pushstring (L, hostname); // 2: hostname - - struct error *e = NULL; - if (!lua_plugin_call (self->plugin, 2, 0, &e)) - { - lua_plugin_log_error (self->plugin, "connector on_success", e); - // The connection has placed itself in the cache - lua_connection_discard (connection); - } - } - - lua_connector_discard (self); -} - -static void -lua_connector_on_failure (void *user_data) -{ - struct lua_connector *self = user_data; - if (self->ref_on_error != LUA_REFNIL) - { - lua_State *L = self->plugin->L; - lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_on_error); - lua_pushstring (L, self->last_error); // 1: error string - - struct error *e = NULL; - if (!lua_plugin_call (self->plugin, 1, 0, &e)) - lua_plugin_log_error (self->plugin, "connector on_error", e); - } - - lua_connector_discard (self); -} - -static void -lua_connector_on_error (void *user_data, const char *error) -{ - struct lua_connector *self = user_data; - free (self->last_error); - self->last_error = xstrdup (error); -} - -static int -lua_plugin_connect (lua_State *L) -{ - struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); - const char *host = luaL_checkstring (L, 1); - const char *service = luaL_checkstring (L, 2); - luaL_checktype (L, 3, LUA_TTABLE); - - struct lua_connector *self = lua_newuserdata (L, sizeof *self); - luaL_setmetatable (L, XLUA_CONNECTOR_METATABLE); - memset (self, 0, sizeof *self); - - self->plugin = plugin; - self->ref_on_success = LUA_REFNIL; - self->ref_on_error = LUA_REFNIL; - - (void) lua_plugin_check_field (L, 3, "on_success", LUA_TFUNCTION, true); - self->ref_on_success = luaL_ref (L, LUA_REGISTRYINDEX); - (void) lua_plugin_check_field (L, 3, "on_error", LUA_TFUNCTION, true); - self->ref_on_error = luaL_ref (L, LUA_REGISTRYINDEX); - - struct app_context *ctx = plugin->ctx; - struct connector *connector = &self->connector; - connector_init (connector, &ctx->poller); - connector_add_target (connector, host, service); - - connector->on_connected = lua_connector_on_connected; - connector->on_connecting = NULL; - connector->on_error = lua_connector_on_error; - connector->on_failure = lua_connector_on_failure; - connector->user_data = self; - - self->active = true; - lua_cache_store (L, self, -1); - return 1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // The script can create as many wait channels as wanted. They only actually // do anything once they get yielded to the main lua_resume() call. @@ -9978,6 +9835,127 @@ lua_plugin_push_wait_timer (struct lua_plugin *plugin, lua_State *L, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +struct lua_wait_dial +{ + struct lua_wait_channel super; ///< The structure we're deriving + + struct lua_plugin *plugin; ///< The plugin we belong to + struct connector connector; ///< Connector object + bool active; ///< Whether the connector is alive + + struct lua_connection *connection; ///< Established connection + char *hostname; ///< Target hostname + char *last_error; ///< Connecting error, if any +}; + +static bool +lua_wait_dial_check (struct lua_wait_channel *wchannel) +{ + struct lua_wait_dial *self = + CONTAINER_OF (wchannel, struct lua_wait_dial, super); + + lua_State *L = self->super.task->thread; + if (self->connection) + { + // FIXME: this way the connection can leak, it shouldn't stay in cache + // automatically all the time but clean itself up on GC + lua_cache_get (L, self->connection); + lua_pushstring (L, self->hostname); + self->connection = NULL; + } + else if (self->last_error) + { + lua_pushnil (L); + lua_pushnil (L); + lua_pushstring (L, self->last_error); + } + else + return false; + return true; +} + +static void +lua_wait_dial_cancel (struct lua_wait_dial *self) +{ + if (self->active) + { + connector_free (&self->connector); + self->active = false; + } +} + +static void +lua_wait_dial_cleanup (struct lua_wait_channel *wchannel) +{ + struct lua_wait_dial *self = + CONTAINER_OF (wchannel, struct lua_wait_dial, super); + + lua_wait_dial_cancel (self); + if (self->connection) + lua_connection_discard (self->connection); + + free (self->hostname); + free (self->last_error); +} + +static void +lua_wait_dial_on_connected (void *user_data, int socket, const char *hostname) +{ + struct lua_wait_dial *self = user_data; + if (self->super.task) + lua_task_wakeup (self->super.task); + + self->connection = lua_plugin_push_connection (self->plugin, socket); + // TODO: use the hostname for SNI once TLS is implemented + self->hostname = xstrdup (hostname); + lua_wait_dial_cancel (self); +} + +static void +lua_wait_dial_on_failure (void *user_data) +{ + struct lua_wait_dial *self = user_data; + if (self->super.task) + lua_task_wakeup (self->super.task); + lua_wait_dial_cancel (self); +} + +static void +lua_wait_dial_on_error (void *user_data, const char *error) +{ + struct lua_wait_dial *self = user_data; + free (self->last_error); + self->last_error = xstrdup (error); +} + +static int +lua_plugin_push_wait_dial (struct lua_plugin *plugin, lua_State *L, + const char *host, const char *service) +{ + struct lua_wait_dial *self = lua_newuserdata (L, sizeof *self); + luaL_setmetatable (L, XLUA_WCHANNEL_METATABLE); + memset (self, 0, sizeof *self); + + self->super.check = lua_wait_dial_check; + self->super.cleanup = lua_wait_dial_cleanup; + + struct connector *connector = &self->connector; + connector_init (connector, &plugin->ctx->poller); + connector_add_target (connector, host, service); + + connector->on_connected = lua_wait_dial_on_connected; + connector->on_connecting = NULL; + connector->on_error = lua_wait_dial_on_error; + connector->on_failure = lua_wait_dial_on_failure; + connector->user_data = self; + + self->plugin = plugin; + self->active = true; + return 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static int lua_async_go (lua_State *L) { @@ -10017,10 +9995,19 @@ lua_async_timer_ms (lua_State *L) return lua_plugin_push_wait_timer (plugin, L, timeout); } +static int +lua_async_dial (lua_State *L) +{ + struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); + return lua_plugin_push_wait_dial (plugin, L, + luaL_checkstring (L, 1), luaL_checkstring (L, 2)); +} + static luaL_Reg lua_async_library[] = { { "go", lua_async_go }, { "timer_ms", lua_async_timer_ms }, + { "dial", lua_async_dial }, { NULL, NULL }, }; @@ -10050,7 +10037,6 @@ static luaL_Reg lua_plugin_library[] = { "hook_prompt", lua_plugin_hook_prompt }, { "hook_completion", lua_plugin_hook_completion }, { "setup_config", lua_plugin_setup_config }, - { "connect", lua_plugin_connect }, // And these are methods: @@ -10332,7 +10318,6 @@ lua_plugin_load (struct app_context *ctx, const char *filename, lua_plugin_reg_weak (L, &lua_server_info, lua_server_table); lua_plugin_reg_meta (L, XLUA_SCHEMA_METATABLE, lua_schema_table); lua_plugin_reg_meta (L, XLUA_CONNECTION_METATABLE, lua_connection_table); - lua_plugin_reg_meta (L, XLUA_CONNECTOR_METATABLE, lua_connector_table); lua_plugin_reg_meta (L, XLUA_TASK_METATABLE, lua_task_table); lua_plugin_reg_meta (L, XLUA_WCHANNEL_METATABLE, lua_wchannel_table); diff --git a/plugins/degesch/last-fm.lua b/plugins/degesch/last-fm.lua index 5c836ca..8f6902d 100644 --- a/plugins/degesch/last-fm.lua +++ b/plugins/degesch/last-fm.lua @@ -118,24 +118,23 @@ end local running -- Initiate a connection to last.fm servers +async, await = degesch.async, coroutine.yield local make_request = function (buffer, action) if not user or not api_key then report_error (buffer, "configuration is incomplete") return end - if running then running.abort () end - - running = degesch.connect ("ws.audioscrobbler.com", 80, { - on_success = function (c, host) - on_connected (buffer, c, host, action) - running = nil - end, - on_error = function (e) + if running then running:cancel () end + running = async.go (function () + local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80)) + if e then report_error (buffer, e) - running = nil + else + on_connected (buffer, c, host, action) end - }) + running = nil + end) end -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -