From 71879a3f64f5f04cdceeedf0317175b2bab1701c Mon Sep 17 00:00:00 2001 From: James Booth Date: Mon, 4 Jul 2016 00:41:29 +0100 Subject: [PATCH] Free plugins commands on quit --- Makefile.am | 1 + src/command/cmd_funcs.c | 2 +- src/command/cmd_funcs.h | 10 +-- src/plugins/api.c | 14 ++-- src/plugins/api.h | 8 +- src/plugins/c_api.c | 14 +++- src/plugins/callbacks.c | 131 ++++++++++++++++++++++++++----- src/plugins/callbacks.h | 6 +- src/plugins/python_api.c | 26 +++--- src/ui/core.c | 2 +- src/ui/ui.h | 2 +- tests/unittests/test_callbacks.c | 32 ++++++++ tests/unittests/test_callbacks.h | 2 + tests/unittests/ui/stub_ui.c | 2 +- tests/unittests/unittests.c | 4 + 15 files changed, 203 insertions(+), 53 deletions(-) create mode 100644 tests/unittests/test_callbacks.c create mode 100644 tests/unittests/test_callbacks.h diff --git a/Makefile.am b/Makefile.am index 61b5783d..bcd964a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,6 +123,7 @@ unittest_sources = \ tests/unittests/test_cmd_join.c tests/unittests/test_cmd_join.h \ tests/unittests/test_cmd_roster.c tests/unittests/test_cmd_roster.h \ tests/unittests/test_cmd_disconnect.c tests/unittests/test_cmd_disconnect.h \ + tests/unittests/test_callbacks.c tests/unittests/test_callbacks.h \ tests/unittests/unittests.c functionaltest_sources = \ diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 70365a60..9b7c36a8 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -3809,7 +3809,7 @@ cmd_form(ProfWin *window, const char *const command, gchar **args) } else { mucconfwin_form_help(confwin); - const gchar **help_text = NULL; + gchar **help_text = NULL; Command *command = cmd_get("/form"); if (command) { diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h index 52b9946e..43731050 100644 --- a/src/command/cmd_funcs.h +++ b/src/command/cmd_funcs.h @@ -39,11 +39,11 @@ // Command help strings typedef struct cmd_help_t { - const gchar *tags[20]; - const gchar *synopsis[50]; - const gchar *desc; - const gchar *args[128][2]; - const gchar *examples[20]; + gchar *tags[20]; + gchar *synopsis[50]; + gchar *desc; + gchar *args[128][2]; + gchar *examples[20]; } CommandHelp; /* diff --git a/src/plugins/api.c b/src/plugins/api.c index ea134fe4..2a577296 100644 --- a/src/plugins/api.c +++ b/src/plugins/api.c @@ -107,18 +107,21 @@ api_cons_bad_cmd_usage(const char *const cmd) void api_register_command(const char *const plugin_name, const char *command_name, int min_args, int max_args, - const char **synopsis, const char *description, const char *arguments[][2], const char **examples, void *callback, - void(*callback_exec)(PluginCommand *command, gchar **args)) + const char **synopsis, const char *description, const char *arguments[][2], const char **examples, + void *callback, void(*callback_exec)(PluginCommand *command, gchar **args), void(*callback_destroy)(void *callback)) { PluginCommand *command = malloc(sizeof(PluginCommand)); - command->command_name = command_name; + command->command_name = strdup(command_name); command->min_args = min_args; command->max_args = max_args; command->callback = callback; command->callback_exec = callback_exec; + command->callback_destroy = callback_destroy; CommandHelp *help = malloc(sizeof(CommandHelp)); + help->tags[0] = NULL; + int i = 0; for (i = 0; synopsis[i] != NULL; i++) { help->synopsis[i] = strdup(synopsis[i]); @@ -140,16 +143,17 @@ api_register_command(const char *const plugin_name, const char *command_name, in command->help = help; - callbacks_add_command(command); + callbacks_add_command(plugin_name, command); } void api_register_timed(const char *const plugin_name, void *callback, int interval_seconds, - void (*callback_exec)(PluginTimedFunction *timed_function)) + void (*callback_exec)(PluginTimedFunction *timed_function), void(*callback_destroy)(void *callback)) { PluginTimedFunction *timed_function = malloc(sizeof(PluginTimedFunction)); timed_function->callback = callback; timed_function->callback_exec = callback_exec; + timed_function->callback_destroy = callback_destroy; timed_function->interval_seconds = interval_seconds; timed_function->timer = g_timer_new(); diff --git a/src/plugins/api.h b/src/plugins/api.h index 571c6168..0b1aec32 100644 --- a/src/plugins/api.h +++ b/src/plugins/api.h @@ -52,9 +52,9 @@ char** api_get_current_occupants(void); void api_register_command(const char *const plugin_name, const char *command_name, int min_args, int max_args, const char **synopsis, const char *description, const char *arguments[][2], const char **examples, - void *callback, void(*callback_func)(PluginCommand *command, gchar **args)); + void *callback, void(*callback_func)(PluginCommand *command, gchar **args), void(*callback_destroy)(void *callback)); void api_register_timed(const char *const plugin_name, void *callback, int interval_seconds, - void (*callback_func)(PluginTimedFunction *timed_function)); + void (*callback_func)(PluginTimedFunction *timed_function), void(*callback_destroy)(void *callback)); void api_completer_add(const char *const plugin_name, const char *key, char **items); void api_completer_remove(const char *key, char **items); @@ -70,8 +70,8 @@ void api_win_create( const char *const plugin_name, const char *tag, void *callback, - void(*destroy)(void *callback), - void(*callback_func)(PluginWindowCallback *window_callback, char *tag, char *line)); + void(*callback_func)(PluginWindowCallback *window_callback, char *tag, char *line), + void(*destroy)(void *callback)); int api_win_focus(const char *tag); int api_win_show(const char *tag, const char *line); int api_win_show_themed(const char *tag, const char *const group, const char *const key, const char *const def, const char *line); diff --git a/src/plugins/c_api.c b/src/plugins/c_api.c index 96673972..32719feb 100644 --- a/src/plugins/c_api.c +++ b/src/plugins/c_api.c @@ -90,7 +90,9 @@ c_api_register_command(const char *filename, const char *command_name, int min_a CommandWrapper *wrapper = malloc(sizeof(CommandWrapper)); wrapper->func = callback; api_register_command(plugin_name, command_name, min_args, max_args, synopsis, - description, arguments, examples, wrapper, c_command_callback); + description, arguments, examples, wrapper, c_command_callback, free); + + free(plugin_name); } static void @@ -101,7 +103,9 @@ c_api_register_timed(const char *filename, void(*callback)(void), int interval_s TimedWrapper *wrapper = malloc(sizeof(TimedWrapper)); wrapper->func = callback; - api_register_timed(plugin_name, wrapper, interval_seconds, c_timed_callback); + api_register_timed(plugin_name, wrapper, interval_seconds, c_timed_callback, free); + + free(plugin_name); } static void @@ -111,6 +115,8 @@ c_api_completer_add(const char *filename, const char *key, char **items) log_debug("Autocomplete add %s for %s", key, plugin_name); api_completer_add(plugin_name, key, items); + + free(plugin_name); } static void @@ -205,7 +211,9 @@ c_api_win_create(const char *filename, char *tag, void(*callback)(char *tag, cha WindowWrapper *wrapper = malloc(sizeof(WindowWrapper)); wrapper->func = callback; - api_win_create(plugin_name, tag, wrapper, free, c_window_callback); + api_win_create(plugin_name, tag, wrapper, c_window_callback, free); + + free(plugin_name); } static int diff --git a/src/plugins/callbacks.c b/src/plugins/callbacks.c index 57a8c09d..2569962c 100644 --- a/src/plugins/callbacks.c +++ b/src/plugins/callbacks.c @@ -44,7 +44,7 @@ #include "ui/ui.h" -static GSList *p_commands = NULL; +static GHashTable *p_commands = NULL; static GSList *p_timed_functions = NULL; static GHashTable *p_window_callbacks = NULL; @@ -57,22 +57,88 @@ _free_window_callback(PluginWindowCallback *window_callback) free(window_callback); } -void -callbacks_init(void) +//typedef struct cmd_help_t { +// const gchar *tags[20]; +// const gchar *synopsis[50]; +// const gchar *desc; +// const gchar *args[128][2]; +// const gchar *examples[20]; +//} CommandHelp; + +static void +_free_command_help(CommandHelp *help) { - p_window_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_free_window_callback); + int i = 0; + while (help->tags[i] != NULL) { + free(help->tags[i++]); + } + + i = 0; + while (help->synopsis[i] != NULL) { + free(help->synopsis[i++]); + } + + free(help->desc); + + i = 0; + while (help->args[i] != NULL && help->args[i][0] != NULL) { + free(help->args[i][0]); + free(help->args[i][1]); + i++; + } + + i = 0; + while (help->examples[i] != NULL) { + free(help->examples[i++]); + } + + free(help); +} + +static void +_free_command(PluginCommand *command) +{ + if (command->callback_destroy) { + command->callback_destroy(command->callback); + } + free(command->command_name); + + _free_command_help(command->help); + free(command); +} + +static void +_free_command_hash(GHashTable *command_hash) +{ + g_hash_table_destroy(command_hash); } +void +callbacks_init(void) +{ + p_commands = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_free_command_hash); + p_window_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_free_window_callback); +} + +// TODO move to plugin destroy functions void callbacks_close(void) { + g_hash_table_destroy(p_commands); g_hash_table_destroy(p_window_callbacks); } void -callbacks_add_command(PluginCommand *command) +callbacks_add_command(const char *const plugin_name, PluginCommand *command) { - p_commands = g_slist_append(p_commands, command); + GHashTable *command_hash = g_hash_table_lookup(p_commands, plugin_name); + if (command_hash) { + g_hash_table_insert(command_hash, strdup(command->command_name), command); + } else { + command_hash = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_free_command); + g_hash_table_insert(command_hash, strdup(command->command_name), command); + g_hash_table_insert(p_commands, strdup(plugin_name), command_hash); + } cmd_ac_add(command->command_name); cmd_ac_add_help(&command->command_name[1]); } @@ -104,25 +170,32 @@ plugins_run_command(const char * const input) { gchar **split = g_strsplit(input, " ", -1); - GSList *p_command = p_commands; - while (p_command) { - PluginCommand *command = p_command->data; - if (g_strcmp0(split[0], command->command_name) == 0) { + GList *command_hashes = g_hash_table_get_values(p_commands); + GList *curr_hash = command_hashes; + while (curr_hash) { + GHashTable *command_hash = curr_hash->data; + + PluginCommand *command = g_hash_table_lookup(command_hash, split[0]); + if (command) { gboolean result; gchar **args = parse_args_with_freetext(input, command->min_args, command->max_args, &result); if (result == FALSE) { ui_invalid_command_usage(command->command_name, NULL); g_strfreev(split); + g_list_free(command_hashes); return TRUE; } else { command->callback_exec(command, args); g_strfreev(split); g_strfreev(args); + g_list_free(command_hashes); return TRUE; } } - p_command = g_slist_next(p_command); + + curr_hash = g_list_next(curr_hash); } + g_strfreev(split); return FALSE; } @@ -130,16 +203,22 @@ plugins_run_command(const char * const input) CommandHelp* plugins_get_help(const char *const cmd) { - GSList *curr = p_commands; - while (curr) { - PluginCommand *command = curr->data; - if (g_strcmp0(cmd, command->command_name) == 0) { + GList *command_hashes = g_hash_table_get_values(p_commands); + GList *curr_hash = command_hashes; + while (curr_hash) { + GHashTable *command_hash = curr_hash->data; + + PluginCommand *command = g_hash_table_lookup(command_hash, cmd); + if (command) { + g_list_free(command_hashes); return command->help; } - curr = g_slist_next(curr); + curr_hash = g_list_next(curr_hash); } + g_list_free(command_hashes); + return NULL; } @@ -167,12 +246,22 @@ plugins_get_command_names(void) { GList *result = NULL; - GSList *curr = p_commands; - while (curr) { - PluginCommand *command = curr->data; - result = g_list_append(result, (char*)command->command_name); - curr = g_slist_next(curr); + GList *command_hashes = g_hash_table_get_values(p_commands); + GList *curr_hash = command_hashes; + while (curr_hash) { + GHashTable *command_hash = curr_hash->data; + GList *commands = g_hash_table_get_keys(command_hash); + GList *curr = commands; + while (curr) { + char *command = curr->data; + result = g_list_append(result, command); + curr = g_list_next(curr); + } + g_list_free(commands); + curr_hash = g_list_next(curr_hash); } + g_list_free(command_hashes); + return result; } diff --git a/src/plugins/callbacks.h b/src/plugins/callbacks.h index 0ef6de9f..fa78d2c6 100644 --- a/src/plugins/callbacks.h +++ b/src/plugins/callbacks.h @@ -40,17 +40,19 @@ #include "command/cmd_defs.h" typedef struct p_command { - const char *command_name; + char *command_name; int min_args; int max_args; CommandHelp *help; void *callback; void (*callback_exec)(struct p_command *command, gchar **args); + void (*callback_destroy)(void *callback); } PluginCommand; typedef struct p_timed_function { void *callback; void (*callback_exec)(struct p_timed_function *timed_function); + void (*callback_destroy)(void *callback); int interval_seconds; GTimer *timer; } PluginTimedFunction; @@ -64,7 +66,7 @@ typedef struct p_window_input_callback { void callbacks_init(void); void callbacks_close(void); -void callbacks_add_command(PluginCommand *command); +void callbacks_add_command(const char *const plugin, PluginCommand *command); void callbacks_add_timed(PluginTimedFunction *timed_function); void callbacks_add_window_handler(const char *tag, PluginWindowCallback *window_callback); void * callbacks_get_window_handler(const char *tag); diff --git a/src/plugins/python_api.c b/src/plugins/python_api.c index 1a60a07c..82cf22eb 100644 --- a/src/plugins/python_api.c +++ b/src/plugins/python_api.c @@ -166,19 +166,18 @@ python_api_register_command(PyObject *self, PyObject *args) allow_python_threads(); api_register_command(plugin_name, command_name, min_args, max_args, c_synopsis, - description, c_arguments, c_examples, p_callback, python_command_callback); + description, c_arguments, c_examples, p_callback, python_command_callback, NULL); disable_python_threads(); } + free(plugin_name); + return Py_BuildValue(""); } static PyObject * python_api_register_timed(PyObject *self, PyObject *args) { - char *plugin_name = _python_plugin_name(); - log_debug("Register timed for %s", plugin_name); - PyObject *p_callback = NULL; int interval_seconds = 0; @@ -186,12 +185,17 @@ python_api_register_timed(PyObject *self, PyObject *args) return Py_BuildValue(""); } + char *plugin_name = _python_plugin_name(); + log_debug("Register timed for %s", plugin_name); + if (p_callback && PyCallable_Check(p_callback)) { allow_python_threads(); - api_register_timed(plugin_name, p_callback, interval_seconds, python_timed_callback); + api_register_timed(plugin_name, p_callback, interval_seconds, python_timed_callback, NULL); disable_python_threads(); } + free(plugin_name); + return Py_BuildValue(""); } @@ -223,6 +227,8 @@ python_api_completer_add(PyObject *self, PyObject *args) api_completer_add(plugin_name, key, c_items); disable_python_threads(); + free(plugin_name); + return Py_BuildValue(""); } @@ -456,19 +462,21 @@ python_api_win_create(PyObject *self, PyObject *args) char *tag = NULL; PyObject *p_callback = NULL; - char *plugin_name = _python_plugin_name(); - log_debug("Win create %s for %s", tag, plugin_name); - if (!PyArg_ParseTuple(args, "sO", &tag, &p_callback)) { return Py_BuildValue(""); } + char *plugin_name = _python_plugin_name(); + log_debug("Win create %s for %s", tag, plugin_name); + if (p_callback && PyCallable_Check(p_callback)) { allow_python_threads(); - api_win_create(plugin_name, tag, p_callback, NULL, python_window_callback); + api_win_create(plugin_name, tag, p_callback, python_window_callback, NULL); disable_python_threads(); } + free(plugin_name); + return Py_BuildValue(""); } diff --git a/src/ui/core.c b/src/ui/core.c index 3a89008f..c687cba6 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -1230,7 +1230,7 @@ ui_handle_room_config_submit_result_error(const char *const roomjid, const char } void -ui_show_lines(ProfWin *window, const gchar** lines) +ui_show_lines(ProfWin *window, gchar** lines) { if (lines) { int i; diff --git a/src/ui/ui.h b/src/ui/ui.h index c15af910..5cf8cb31 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -110,7 +110,7 @@ void ui_goodbye_title(void); void ui_handle_room_configuration_form_error(const char *const roomjid, const char *const message); void ui_handle_room_config_submit_result(const char *const roomjid); void ui_handle_room_config_submit_result_error(const char *const roomjid, const char *const message); -void ui_show_lines(ProfWin *window, const gchar** lines); +void ui_show_lines(ProfWin *window, gchar** lines); void ui_redraw_all_room_rosters(void); void ui_show_all_room_rosters(void); void ui_hide_all_room_rosters(void); diff --git a/tests/unittests/test_callbacks.c b/tests/unittests/test_callbacks.c new file mode 100644 index 00000000..32959aa7 --- /dev/null +++ b/tests/unittests/test_callbacks.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "plugins/callbacks.h" +#include "plugins/plugins.h" + +void returns_no_commands(void **state) +{ + callbacks_init(); + GList *commands = plugins_get_command_names(); + + assert_true(commands == NULL); +} + +void returns_commands(void **state) +{ + callbacks_init(); + PluginCommand *command = malloc(sizeof(PluginCommand)); + command->command_name = strdup("something"); + callbacks_add_command("Cool plugin", command); + + GList *commands = plugins_get_command_names(); + assert_true(g_list_length(commands) == 1); + + char *name = commands->data; + assert_string_equal(name, "something"); +} diff --git a/tests/unittests/test_callbacks.h b/tests/unittests/test_callbacks.h new file mode 100644 index 00000000..35751d2e --- /dev/null +++ b/tests/unittests/test_callbacks.h @@ -0,0 +1,2 @@ +void returns_no_commands(void **state); +void returns_commands(void **state); diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c index 5e86799a..b0635fb8 100644 --- a/tests/unittests/ui/stub_ui.c +++ b/tests/unittests/ui/stub_ui.c @@ -265,7 +265,7 @@ void mucconfwin_show_form(ProfMucConfWin *confwin) {} void mucconfwin_show_form_field(ProfMucConfWin *confwin, DataForm *form, char *tag) {} void mucconfwin_form_help(ProfMucConfWin *confwin) {} void mucconfwin_field_help(ProfMucConfWin *confwin, char *tag) {} -void ui_show_lines(ProfWin *window, const gchar** lines) {} +void ui_show_lines(ProfWin *window, gchar** lines) {} void ui_redraw_all_room_rosters(void) {} void ui_show_all_room_rosters(void) {} void ui_hide_all_room_rosters(void) {} diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index 5577104e..91fb3cb2 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -33,6 +33,7 @@ #include "test_cmd_roster.h" #include "test_cmd_disconnect.h" #include "test_form.h" +#include "test_callbacks.h" int main(int argc, char* argv[]) { const UnitTest all_tests[] = { @@ -602,6 +603,9 @@ int main(int argc, char* argv[]) { unit_test(prof_partial_occurrences_tests), unit_test(prof_whole_occurrences_tests), + + unit_test(returns_no_commands), + unit_test(returns_commands), }; return run_tests(all_tests);