src/nexus.c
author Tero Marttila <terom@fixme.fi>
Mon, 16 Mar 2009 00:55:07 +0200
changeset 63 d399a1d915a3
parent 61 4ba21936518a
child 65 d7508879ad01
permissions -rw-r--r--
start reworking option-parsing, but --module/--config is still unimplemented
#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',
    
    /** 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      },
    {0,                 0,  0,      0               },
};

/**
 * Display --help output on stdout
 */
static void usage (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");
}

/**
 * Parse and apply a --network option
 */
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
    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
 */
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
    if (irc_net_add_chan(net, NULL, &info, 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(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:
            case OPT_CONFIG:
                RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "option unimplemented");

            case '?':
                usage(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;
    
    if (ctx->client && !ctx->client->quitting) {
        log_info("Quitting...");

        // quit it
        irc_client_quit(ctx->client, "Goodbye, cruel world ;(");

    } else {
        log_error("Aborting");
        
        // die
        if (ctx->client) {
            irc_client_destroy(ctx->client);
            ctx->client = NULL;
        }

        // exit
        event_base_loopexit(ctx->ev_base, NULL);
    }
}

int main (int argc, char **argv) 
{
    struct signals *signals;
    struct error_info err;

    struct nexus _nexus, *nexus = &_nexus;

    // 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(&signals, nexus->ev_base)))
        FATAL("signals_create");

    // add our signal handlers
    if (
            (ERROR_CODE(&err) = signals_add(signals, SIGPIPE, &signals_ignore, signals))
        ||  (ERROR_CODE(&err) = signals_add(signals, SIGINT, &on_sigint, nexus))
    )
        FATAL_ERROR(&err, "signals_add");
 
    // 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
    if (event_base_dispatch(nexus->ev_base))
        FATAL("event_base_dispatch");
    
    // ok, no cleanup
    return 0;
}