#include "blade.h" #include "tap.h" #define CONSOLE_INPUT_MAX 512 ks_bool_t g_shutdown = KS_FALSE; void loop(blade_handle_t *bh); void process_console_input(blade_handle_t *bh, char *line); typedef void (*command_callback)(blade_handle_t *bh, char *args); struct command_def_s { const char *cmd; command_callback callback; }; void command_quit(blade_handle_t *bh, char *args); static const struct command_def_s command_defs[] = { { "quit", command_quit }, { NULL, NULL } }; ks_status_t blade_module_chat_create(blade_module_t **bmP, blade_handle_t *bh); ks_status_t blade_module_chat_on_startup(blade_module_t *bm, config_setting_t *config); ks_status_t blade_module_chat_on_shutdown(blade_module_t *bm); typedef struct blade_module_chat_s blade_module_chat_t; struct blade_module_chat_s { blade_handle_t *handle; ks_pool_t *pool; ks_thread_pool_t *tpool; blade_module_t *module; //blade_module_callbacks_t *module_callbacks; blade_space_t *blade_chat_space; const char *session_state_callback_id; ks_list_t *participants; }; void blade_module_chat_on_session_state(blade_session_t *bs, blade_session_state_condition_t condition, void *data); ks_bool_t blade_chat_join_request_handler(blade_module_t *bm, blade_request_t *breq); ks_bool_t blade_chat_leave_request_handler(blade_module_t *bm, blade_request_t *breq); ks_bool_t blade_chat_send_request_handler(blade_module_t *bm, blade_request_t *breq); static blade_module_callbacks_t g_module_chat_callbacks = { blade_module_chat_on_startup, blade_module_chat_on_shutdown, }; int main(int argc, char **argv) { blade_handle_t *bh = NULL; config_t config; config_setting_t *config_blade = NULL; blade_module_t *mod_chat = NULL; //blade_identity_t *id = NULL; const char *cfgpath = "blades.cfg"; ks_global_set_default_logger(KS_LOG_LEVEL_DEBUG); blade_init(); blade_handle_create(&bh); //if (argc > 1) cfgpath = argv[1]; config_init(&config); if (!config_read_file(&config, cfgpath)) { ks_log(KS_LOG_ERROR, "%s:%d - %s\n", config_error_file(&config), config_error_line(&config), config_error_text(&config)); config_destroy(&config); return EXIT_FAILURE; } config_blade = config_lookup(&config, "blade"); if (!config_blade) { ks_log(KS_LOG_ERROR, "Missing 'blade' config group\n"); config_destroy(&config); return EXIT_FAILURE; } if (config_setting_type(config_blade) != CONFIG_TYPE_GROUP) { ks_log(KS_LOG_ERROR, "The 'blade' config setting is not a group\n"); return EXIT_FAILURE; } // must occur before startup blade_module_chat_create(&mod_chat, bh); blade_handle_module_register(mod_chat); if (blade_handle_startup(bh, config_blade) != KS_STATUS_SUCCESS) { ks_log(KS_LOG_ERROR, "Blade startup failed\n"); return EXIT_FAILURE; } loop(bh); blade_handle_destroy(&bh); config_destroy(&config); blade_shutdown(); return 0; } void loop(blade_handle_t *bh) { char buf[CONSOLE_INPUT_MAX]; while (!g_shutdown) { if (!fgets(buf, CONSOLE_INPUT_MAX, stdin)) break; for (int index = 0; buf[index]; ++index) { if (buf[index] == '\r' || buf[index] == '\n') { buf[index] = '\0'; break; } } process_console_input(bh, buf); } } void parse_argument(char **input, char **arg, char terminator) { char *tmp; ks_assert(input); ks_assert(*input); ks_assert(arg); tmp = *input; *arg = tmp; while (*tmp && *tmp != terminator) ++tmp; if (*tmp == terminator) { *tmp = '\0'; ++tmp; } *input = tmp; } void process_console_input(blade_handle_t *bh, char *line) { char *args = line; char *cmd = NULL; ks_bool_t found = KS_FALSE; ks_log(KS_LOG_DEBUG, "Output: %s\n", line); parse_argument(&args, &cmd, ' '); ks_log(KS_LOG_DEBUG, "Command: %s, Args: %s\n", cmd, args); for (int32_t index = 0; command_defs[index].cmd; ++index) { if (!strcmp(command_defs[index].cmd, cmd)) { found = KS_TRUE; command_defs[index].callback(bh, args); } } if (!found) ks_log(KS_LOG_INFO, "Command '%s' unknown.\n", cmd); } void command_quit(blade_handle_t *bh, char *args) { ks_assert(bh); ks_assert(args); ks_log(KS_LOG_DEBUG, "Shutting down\n"); g_shutdown = KS_TRUE; } static void blade_module_chat_cleanup(ks_pool_t *pool, void *ptr, void *arg, ks_pool_cleanup_action_t action, ks_pool_cleanup_type_t type) { blade_module_chat_t *bm_chat = (blade_module_chat_t *)ptr; ks_assert(bm_chat); switch (action) { case KS_MPCL_ANNOUNCE: break; case KS_MPCL_TEARDOWN: break; case KS_MPCL_DESTROY: break; } } ks_status_t blade_module_chat_create(blade_module_t **bmP, blade_handle_t *bh) { blade_module_chat_t *bm_chat = NULL; ks_pool_t *pool = NULL; ks_assert(bmP); ks_assert(bh); ks_pool_open(&pool); ks_assert(pool); bm_chat = ks_pool_alloc(pool, sizeof(blade_module_chat_t)); bm_chat->handle = bh; bm_chat->pool = pool; bm_chat->tpool = blade_handle_tpool_get(bh); bm_chat->session_state_callback_id = NULL; ks_list_create(&bm_chat->participants, pool); ks_assert(bm_chat->participants); blade_module_create(&bm_chat->module, bh, pool, bm_chat, &g_module_chat_callbacks); ks_assert(bm_chat->module); ks_pool_set_cleanup(pool, bm_chat, NULL, blade_module_chat_cleanup); ks_log(KS_LOG_DEBUG, "Created\n"); *bmP = bm_chat->module; return KS_STATUS_SUCCESS; } ks_status_t blade_module_chat_config(blade_module_chat_t *bm_chat, config_setting_t *config) { config_setting_t *chat = NULL; ks_assert(bm_chat); ks_assert(config); if (!config_setting_is_group(config)) { ks_log(KS_LOG_DEBUG, "!config_setting_is_group(config)\n"); return KS_STATUS_FAIL; } chat = config_setting_get_member(config, "chat"); if (chat) { } // Configuration is valid, now assign it to the variables that are used // If the configuration was invalid, then this does not get changed ks_log(KS_LOG_DEBUG, "Configured\n"); return KS_STATUS_SUCCESS; } ks_status_t blade_module_chat_on_startup(blade_module_t *bm, config_setting_t *config) { blade_module_chat_t *bm_chat = NULL; blade_space_t *space = NULL; blade_method_t *method = NULL; ks_assert(bm); ks_assert(config); bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); if (blade_module_chat_config(bm_chat, config) != KS_STATUS_SUCCESS) { ks_log(KS_LOG_DEBUG, "blade_module_chat_config failed\n"); return KS_STATUS_FAIL; } blade_space_create(&space, bm_chat->handle, bm, "blade.chat"); ks_assert(space); bm_chat->blade_chat_space = space; blade_method_create(&method, space, "join", blade_chat_join_request_handler); ks_assert(method); blade_space_methods_add(space, method); blade_method_create(&method, space, "leave", blade_chat_leave_request_handler); ks_assert(method); blade_space_methods_add(space, method); blade_method_create(&method, space, "send", blade_chat_send_request_handler); ks_assert(method); blade_space_methods_add(space, method); blade_handle_space_register(space); blade_handle_session_state_callback_register(blade_module_handle_get(bm), bm, blade_module_chat_on_session_state, &bm_chat->session_state_callback_id); ks_log(KS_LOG_DEBUG, "Started\n"); return KS_STATUS_SUCCESS; } ks_status_t blade_module_chat_on_shutdown(blade_module_t *bm) { blade_module_chat_t *bm_chat = NULL; ks_assert(bm); bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); ks_assert(bm_chat); if (bm_chat->session_state_callback_id) blade_handle_session_state_callback_unregister(blade_module_handle_get(bm), bm_chat->session_state_callback_id); bm_chat->session_state_callback_id = NULL; if (bm_chat->blade_chat_space) blade_handle_space_unregister(bm_chat->blade_chat_space); ks_log(KS_LOG_DEBUG, "Stopped\n"); return KS_STATUS_SUCCESS; } void blade_module_chat_on_session_state(blade_session_t *bs, blade_session_state_condition_t condition, void *data) { blade_module_t *bm = NULL; blade_module_chat_t *bm_chat = NULL; ks_assert(bs); ks_assert(data); bm = (blade_module_t *)data; bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); ks_assert(bm_chat); if (blade_session_state_get(bs) == BLADE_SESSION_STATE_HANGUP && condition == BLADE_SESSION_STATE_CONDITION_PRE) { cJSON *props = NULL; ks_log(KS_LOG_DEBUG, "Removing session from chat participants if present\n"); props = blade_session_properties_get(bs); ks_assert(props); cJSON_DeleteItemFromObject(props, "blade.chat.participant"); ks_list_delete(bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and search manually, also free the id } } ks_bool_t blade_chat_join_request_handler(blade_module_t *bm, blade_request_t *breq) { blade_module_chat_t *bm_chat = NULL; blade_session_t *bs = NULL; cJSON *res = NULL; cJSON *props = NULL; cJSON *props_participant = NULL; ks_assert(bm); ks_assert(breq); ks_log(KS_LOG_DEBUG, "Request Received!\n"); bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); ks_assert(bm_chat); bs = blade_handle_sessions_get(breq->handle, breq->session_id); ks_assert(bs); // @todo properties only used to demonstrate a flexible container for session data, should just rely on the participants list/hash blade_session_properties_write_lock(bs, KS_TRUE); props = blade_session_properties_get(bs); ks_assert(props); props_participant = cJSON_GetObjectItem(props, "blade.chat.participant"); if (props_participant && props_participant->type == cJSON_True) { ks_log(KS_LOG_DEBUG, "Session (%s) attempted to join chat but is already a participant\n", blade_session_id_get(bs)); blade_rpc_error_create(&res, NULL, breq->message_id, -10000, "Already a participant of chat"); } else { ks_log(KS_LOG_DEBUG, "Session (%s) joined chat\n", blade_session_id_get(bs)); if (props_participant) props_participant->type = cJSON_True; else cJSON_AddTrueToObject(props, "blade.chat.participant"); ks_list_append(bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and cleanup when removed blade_rpc_response_create(&res, NULL, breq->message_id); // @todo create an event to send to participants when a session joins and leaves, send after main response though } blade_session_properties_write_unlock(bs); blade_session_send(bs, res, NULL); blade_session_read_unlock(bs); cJSON_Delete(res); return KS_FALSE; } ks_bool_t blade_chat_leave_request_handler(blade_module_t *bm, blade_request_t *breq) { blade_module_chat_t *bm_chat = NULL; blade_session_t *bs = NULL; cJSON *res = NULL; cJSON *props = NULL; cJSON *props_participant = NULL; ks_assert(bm); ks_assert(breq); ks_log(KS_LOG_DEBUG, "Request Received!\n"); bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); ks_assert(bm_chat); bs = blade_handle_sessions_get(breq->handle, breq->session_id); ks_assert(bs); blade_session_properties_write_lock(bs, KS_TRUE); props = blade_session_properties_get(bs); ks_assert(props); props_participant = cJSON_GetObjectItem(props, "blade.chat.participant"); if (!props_participant || props_participant->type == cJSON_False) { ks_log(KS_LOG_DEBUG, "Session (%s) attempted to leave chat but is not a participant\n", blade_session_id_get(bs)); blade_rpc_error_create(&res, NULL, breq->message_id, -10000, "Not a participant of chat"); } else { ks_log(KS_LOG_DEBUG, "Session (%s) left chat\n", blade_session_id_get(bs)); cJSON_DeleteItemFromObject(props, "blade.chat.participant"); ks_list_delete(bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and search manually, also free the id blade_rpc_response_create(&res, NULL, breq->message_id); // @todo create an event to send to participants when a session joins and leaves, send after main response though } blade_session_properties_write_unlock(bs); blade_session_send(bs, res, NULL); blade_session_read_unlock(bs); cJSON_Delete(res); return KS_FALSE; } ks_bool_t blade_chat_send_request_handler(blade_module_t *bm, blade_request_t *breq) { blade_module_chat_t *bm_chat = NULL; blade_session_t *bs = NULL; cJSON *params = NULL; cJSON *res = NULL; cJSON *event = NULL; const char *message = NULL; ks_bool_t sendevent = KS_FALSE; ks_assert(bm); ks_assert(breq); ks_log(KS_LOG_DEBUG, "Request Received!\n"); bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); ks_assert(bm_chat); params = cJSON_GetObjectItem(breq->message, "params"); // @todo cache this in blade_request_t for quicker/easier access if (!params) { ks_log(KS_LOG_DEBUG, "Session (%s) attempted to send chat message with no 'params' object\n", blade_session_id_get(bs)); blade_rpc_error_create(&res, NULL, breq->message_id, -32602, "Missing params object"); } else if (!(message = cJSON_GetObjectCstr(params, "message"))) { ks_log(KS_LOG_DEBUG, "Session (%s) attempted to send chat message with no 'message'\n", blade_session_id_get(bs)); blade_rpc_error_create(&res, NULL, breq->message_id, -32602, "Missing params message string"); } bs = blade_handle_sessions_get(breq->handle, breq->session_id); ks_assert(bs); if (!res) { blade_rpc_response_create(&res, NULL, breq->message_id); sendevent = KS_TRUE; } blade_session_send(bs, res, NULL); blade_session_read_unlock(bs); cJSON_Delete(res); if (sendevent) { blade_rpc_event_create(&event, &res, "blade.chat.message"); ks_assert(event); cJSON_AddStringToObject(res, "from", breq->session_id); // @todo should really be the identity, but we don't have that in place yet cJSON_AddStringToObject(res, "message", message); blade_handle_sessions_send(breq->handle, bm_chat->participants, NULL, event); cJSON_Delete(event); } return KS_FALSE; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: */