render_remote.c
author Tero Marttila <terom@fixme.fi>
Sat, 31 May 2008 19:35:21 +0300
changeset 2 69f8c0acaac7
child 3 675be0a45157
permissions -rw-r--r--
working web_main that uses render_remote

committer: Tero Marttila <terom@fixme.fi>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include "render_remote.h"
#include "common.h"

struct remote_render_ctx {
    struct event ev_conn;
    struct bufferevent *data_bev;

    #pragma pack(push)
    #pragma pack(1)

    struct {
        u_int8_t    mode;

        u_int32_t   img_w;
        u_int32_t   img_h;

        double      x1;
        double      y1;
        double      x2;
        double      y2;
    } render_cmd;

    #pragma pack(pop)

    void (*cb_sent)(void *arg);
    void (*cb_data)(struct evbuffer *buf, void *arg);
    void (*cb_done)(void *arg);
    void (*cb_fail)(void *arg);

    void *cb_arg;
};

void _remote_render_ctx_free (struct remote_render_ctx **ctx) {
    // free the data_bev
    if ((*ctx)->data_bev) {
        bufferevent_free((*ctx)->data_bev);
        (*ctx)->data_bev = NULL;
    }
    
    // free the context structure
    free(*ctx);
    
    *ctx = NULL;
}

#define RENDER_FAILED(ctx, desc) \
    do {                                        \
        perror(desc);                           \
        ctx->cb_fail(ctx->cb_arg);              \
        _remote_render_ctx_free(&ctx);          \
    } while (0)

void _remote_write (struct bufferevent *bev, void *arg) {
    struct remote_render_ctx *ctx = arg;

    // the write buffer was drained, so the render command was sent
    ctx->cb_sent(ctx->cb_arg);
    
    // we don't care about EV_WRITE anymore
    if (bufferevent_disable(ctx->data_bev, EV_WRITE))
        RENDER_FAILED(ctx, "render_remote: bufferevent_disable");

    // start receiving data
    if (bufferevent_enable(ctx->data_bev, EV_READ))
        RENDER_FAILED(ctx, "render_remote: bufferevent_enable");
}

void _remote_read (struct bufferevent *bev, void *arg) {
    struct remote_render_ctx *ctx = arg;
    
    // pass the bufferevent's input buffer to our callback - libevent doesn't provide any function to access this, but hopefully this works correctly
    ctx->cb_data(bev->input, ctx->cb_arg);
}

void _remote_error (struct bufferevent *bev, short what, void *arg) {
    struct remote_render_ctx *ctx = arg;

    // OH NOES; WHAT DO WE DO!?
    
    if (what & EVBUFFER_EOF) {
        // great!
        ctx->cb_done(ctx->cb_arg);

    } else if (what & EVBUFFER_ERROR) {
        // crap.
        perr("render_remote");

        ctx->cb_fail(ctx->cb_arg);

    } else if (what & EVBUFFER_TIMEOUT) {
        // ah well
        error("render_remote: timeout");

        ctx->cb_fail(ctx->cb_arg);

    } else {
        err_exit("weird bufferevent error code: 0x%02X", what);
    }

    // free resources
    _remote_render_ctx_free(&ctx);
}

void _remote_connected (int fd, short event, void *arg) {
    struct remote_render_ctx *ctx = arg;

    // set up the read/write bufferevent
    if ((ctx->data_bev = bufferevent_new(fd, &_remote_read, &_remote_write, &_remote_error, ctx)) == NULL)
        RENDER_FAILED(ctx, "render_remote: bufferevent_new");

    // write the render command
    if (bufferevent_write(ctx->data_bev, &ctx->render_cmd, sizeof(ctx->render_cmd)))
        RENDER_FAILED(ctx, "render_remote: bufferevent_write");

    // wait for it to be written out
    if (bufferevent_enable(ctx->data_bev, EV_WRITE))
        RENDER_FAILED(ctx, "render_remote: bufferevent_enable");
}

void render_cmd_build (render_t *rctx, struct remote_render_ctx *rrctx) {
    // just copy over the render params to the render_cmd
    rrctx->render_cmd.mode = rctx->mode;
    rrctx->render_cmd.img_w = htonl(rctx->img_w);
    rrctx->render_cmd.img_h = htonl(rctx->img_h);
    rrctx->render_cmd.x1 = rctx->x1;
    rrctx->render_cmd.y1 = rctx->y1;
    rrctx->render_cmd.x2 = rctx->x2;
    rrctx->render_cmd.y2 = rctx->y2;
}

int render_remote (
        render_t *render_ctx,
        struct sockaddr_storage *remote,
        void (*cb_sent)(void *arg),
        void (*cb_data)(struct evbuffer *buf, void *arg),
        void (*cb_done)(void *arg),
        void (*cb_fail)(void *arg),
        void *cb_arg
) {    
    // alloc the remote render ctx
    struct remote_render_ctx *ctx = malloc(sizeof(struct remote_render_ctx));

    if (!ctx) {
        error("render_remote: malloc");
        return -1;
    }
    
    // store the provided callback functions
    ctx->cb_sent = cb_sent;
    ctx->cb_data = cb_data;
    ctx->cb_done = cb_done;
    ctx->cb_fail = cb_fail;
    ctx->cb_arg = cb_arg;
    
    // copy the relevant stuff from the render_ctx
    render_cmd_build(render_ctx, ctx);
    
    // create the socket
    int sock = socket(remote->ss_family, SOCK_STREAM, 0);

    if (sock < 0) {
        free(ctx);
        perror("render_remote: socket");
        return -1;
    }

    // mark it as nonblocking
    if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
        free(ctx);
        close(sock);
        perror("render_remote: fcntl");
        return -1;
    }
    
    // initiate the connect
    int err = connect(sock, (struct sockaddr *) remote, sizeof(*remote));

    if (err != -1 || errno != EINPROGRESS) {
        free(ctx);
        close(sock);
        perror("render_remote: connect");
        return -1;
    }

    // do the libevent dance
    event_set(&ctx->ev_conn, sock, EV_WRITE, &_remote_connected, ctx);

    if (event_add(&ctx->ev_conn, NULL)) {
        free(ctx);
        close(sock);
        error("render_remote: event_add");
        return -1;
    }
    
    // success
    return 0;
}