src/nexus.c
author Tero Marttila <terom@fixme.fi>
Fri, 27 Mar 2009 17:22:43 +0200
changeset 83 c8e2dac08207
parent 79 e26f972300e4
child 92 99661e5aac91
permissions -rw-r--r--
add config module and modify irc_log/nexus to use it
#include "nexus.h"
#include "signals.h"
#include "log.h"

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>

#define DEFAULT_HOST "irc.fixme.fi"
#define DEFAULT_PORT "6667"
#define DEFAULT_PORT_SSL "6697"

/**
 * Command-line option codes
 */
enum option_code {
    OPT_HELP            = 'h',
    OPT_NETWORK         = 'n',
    OPT_CHANNEL         = 'c',
    OPT_MODULE          = 'm',
    OPT_CONFIG          = 'C',
    OPT_DEBUG           = 'd',
    
    /** Options without short names */
    _OPT_EXT_BEGIN      = 0x00ff,

};

/**
 * Command-line option definitions
 */
static struct option options[] = {
    {"help",            0,  NULL,   OPT_HELP        },
    {"network",         1,  NULL,   OPT_NETWORK     },
    {"channel",         1,  NULL,   OPT_CHANNEL     },
    {"module",          1,  NULL,   OPT_MODULE      },
    {"config",          1,  NULL,   OPT_CONFIG      },
    {"debug",           0,  NULL,   OPT_DEBUG       },
    {0,                 0,  0,      0               },
};

/**
 * Display --help output on stdout.
 *
 * If nexus is given, --config options for loaded modules are also listed
 */
static void usage (struct nexus *nexus, const char *exe)
{
    printf("Usage: %s [OPTIONS]\n", exe);
    printf("\n");
    printf(" --help / -h            display this message\n");
    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl]]' format\n");
    printf(" --channel / -c         add an IRC channel using '<network>:<channel>' format\n");
    printf(" --module / -m          add a module using '<name>:<path>' format\n");
    printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
    printf(" --debug / -d           set logging level to DEBUG\n");

    if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
        struct module *module;

        printf("\n");
        printf("Module configuration options\n");

        TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
            printf("\n");
            printf("%s:\n", module->info.name);

            if (module->funcs->config_options) {
                const struct config_option *opt;

                for (opt = module->funcs->config_options; opt->name && opt->type; opt++) {
                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, opt->description, opt->help);
                }

            } else {
                printf("\t???\n");
            }
        }
    }
}

/**
 * Parse and apply a --network option, adding the given network to our irc_client.
 */
static err_t apply_network (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct irc_net_info info = {
        .network            = NULL,
        .hostname           = NULL,
        .service            = DEFAULT_PORT,
        .use_ssl            = false,
        .register_info      = {
            .nickname       = "SpBotDev",
            .username       = "spbot-dev",
            .realname       = "SpBot (development version)"
        }
    };

    // parse the required fields
    if ((info.network = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
    
    if ((info.hostname = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <hostname> field for --network");
    
    // parse the optional fields
    if (opt)
        info.service = strsep(&opt, ":");
    
    // parse any remaining flags
    while (opt) {
        char *flag = strsep(&opt, ":");

        if (strcmp(flag, "ssl"))
            info.use_ssl = true;

        else
            RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
    }

    // create the net
    log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);

    if (irc_client_add_net(nexus->client, NULL, &info, err))
        return ERROR_CODE(err);

    // ok
    return SET_ERROR(err, SUCCESS);
}

/**
 * Parse and apply a --channel option, adding the given channel to the given irc_net.
 */
static err_t apply_channel (struct nexus *nexus, char *opt, struct error_info *err)
{
    const char *network = NULL;
    struct irc_net *net;
    struct irc_chan_info info = {
        .channel            = NULL,
    };

    // parse the required fields
    if ((network = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <network> field for --channel");
    
    if ((info.channel = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <channel> field for --channel");

    // trailing garbage?
    if (opt)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");

    // look up the net
    if ((net = irc_client_get_net(nexus->client, network)) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown network for --channel");
    
    // add the channel
    log_info("add channel '%s' on network '%s'", info.channel, net->info.network);

    if (irc_net_add_chan(net, NULL, &info, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Parse and apply a --module option, loading the given module.
 */
static err_t apply_module (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct module_info info = {
        .name       = NULL,
        .path       = NULL,
    };
    struct module *module;

    // parse the required fields
    if ((info.name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --module");

    if ((info.path = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <path> field for --module");

    // trailing garbage?
    if (opt)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
    
    // load it
    log_info("loading module '%s' from path '%s'", info.name, info.path);

    if (module_load(nexus->modules, &module, &info, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Parse and apply a --config option, calling the module's conf func.
 */
static err_t apply_config (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct module *module;
    const char *module_name, *conf_name;
    char *conf_value;

    // parse the required fields
    if ((module_name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <module> field for --config");

    if ((conf_name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --config");

    // value is the rest of the data, might be NULL
    conf_value = opt;

    // lookup the module
    if ((module = module_get(nexus->modules, module_name)) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown module for --config");
    
    // do the config
    log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);

    if (module_conf(module, conf_name, conf_value, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Parse arguments and apply them to the given nexus
 */
static err_t parse_args (struct nexus *nexus, int argc, char **argv, struct error_info *err)
{
    int opt, option_index;
    
    // parse options
    while ((opt = getopt_long(argc, argv, "hn:c:m:C:", options, &option_index)) != -1) {
        switch (opt) {
            case OPT_HELP:
                usage(nexus, argv[0]);

                // XXX: return instead
                exit(EXIT_SUCCESS);
            
            case OPT_NETWORK:
                if (apply_network(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;

            case OPT_CHANNEL:
                if (apply_channel(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;
            
            case OPT_MODULE:
                if (apply_module(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;

            case OPT_CONFIG:
                if (apply_config(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;
            
            case OPT_DEBUG:
                set_log_level(LOG_DEBUG);
                break;

            case '?':
                usage(nexus, argv[0]);
                return SET_ERROR(err, ERR_CMD_OPT);
        }
    }
    
    // ok
    return SUCCESS;
}

static void on_sigint (evutil_socket_t sig, short what, void *arg)
{
    struct nexus *ctx = arg;

    (void) sig;
    (void) what;
    
    log_info("Quitting...");

    // unload the modules
    modules_unload(ctx->modules);

    // quit the irc client
    irc_client_quit(ctx->client, "Goodbye, cruel world ;(");
    
    // remove the signal handlers (ourself)
    signals_free(ctx->signals);

    // now event_base_dispatch should return once everythings' shut down...
}

int main (int argc, char **argv) 
{
    struct nexus _nexus, *nexus = &_nexus;
    struct error_info err;

    // zero nexus
    memset(nexus, 0, sizeof(*nexus));

    // initialize libevent
    if ((nexus->ev_base = event_base_new()) == NULL)
        FATAL("event_base_new");
    
    // initialize signal handlers
    if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
        FATAL("signals_create");

    // add our signal handlers
    if (signal_ignore(SIGPIPE, &err))
        FATAL_ERROR(&err, "signals_ignore(SIGPIPE)");

    if ((ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, &on_sigint, nexus)))
        FATAL_ERROR(&err, "signals_add(SIGINT)");
 
    // initialize sock module
    if (sock_init(nexus->ev_base, &err))
        FATAL_ERROR(&err, "sock_init");

    // modules 
    if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus)))
        FATAL_ERROR(&err, "modules_create");
    
    // the IRC client
    if (irc_client_create(&nexus->client, &err))
        FATAL_ERROR(&err, "irc_client_create");

    // parse args
    if (parse_args(nexus, argc, argv, &err))
        FATAL_ERROR(&err, "parse_args");
    
    // run event loop
    log_info("entering event loop");

    if (event_base_dispatch(nexus->ev_base))
        FATAL("event_base_dispatch");
    
    // ok, no cleanup
    return 0;
}