summaryrefslogtreecommitdiff
path: root/src/libwebvi
diff options
context:
space:
mode:
authorAntti Ajanki <antti.ajanki@iki.fi>2013-08-06 09:07:49 +0300
committerAntti Ajanki <antti.ajanki@iki.fi>2013-08-06 09:07:49 +0300
commit7c81286a59639e139ac7e947378be24410701a5e (patch)
tree88e43b758dc2330e8711ebae80eee0039cc57322 /src/libwebvi
downloadvdr-plugin-webvideo-7c81286a59639e139ac7e947378be24410701a5e.tar.gz
vdr-plugin-webvideo-7c81286a59639e139ac7e947378be24410701a5e.tar.bz2
import to vdr-developer repo
Diffstat (limited to 'src/libwebvi')
-rw-r--r--src/libwebvi/CMakeLists.txt50
-rw-r--r--src/libwebvi/libwebvi.c338
-rw-r--r--src/libwebvi/libwebvi.h370
-rw-r--r--src/libwebvi/link.c86
-rw-r--r--src/libwebvi/link.h45
-rw-r--r--src/libwebvi/linkextractor.c138
-rw-r--r--src/libwebvi/linkextractor.h34
-rw-r--r--src/libwebvi/linktemplates.c172
-rw-r--r--src/libwebvi/linktemplates.h34
-rw-r--r--src/libwebvi/mainmenu.c120
-rw-r--r--src/libwebvi/mainmenu.h27
-rw-r--r--src/libwebvi/menubuilder.c99
-rw-r--r--src/libwebvi/menubuilder.h37
-rw-r--r--src/libwebvi/pipecomponent.c689
-rw-r--r--src/libwebvi/pipecomponent.h95
-rw-r--r--src/libwebvi/request.c237
-rw-r--r--src/libwebvi/request.h42
-rw-r--r--src/libwebvi/urlutils.c132
-rw-r--r--src/libwebvi/urlutils.h32
-rw-r--r--src/libwebvi/webvicontext.c409
-rw-r--r--src/libwebvi/webvicontext.h63
21 files changed, 3249 insertions, 0 deletions
diff --git a/src/libwebvi/CMakeLists.txt b/src/libwebvi/CMakeLists.txt
new file mode 100644
index 0000000..6e4e155
--- /dev/null
+++ b/src/libwebvi/CMakeLists.txt
@@ -0,0 +1,50 @@
+SET(LIBWEBVI_SOURCES
+ libwebvi.c
+ mainmenu.c
+ webvicontext.c
+ request.c
+ link.c
+ linktemplates.c
+ linkextractor.c
+ menubuilder.c
+ pipecomponent.c
+ urlutils.c)
+
+SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
+
+INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/libwebvi)
+
+ADD_LIBRARY(webvi SHARED ${LIBWEBVI_SOURCES})
+ADD_LIBRARY(webvistatic STATIC ${LIBWEBVI_SOURCES})
+
+SET_TARGET_PROPERTIES(webvi PROPERTIES VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
+SET_TARGET_PROPERTIES(webvi PROPERTIES SOVERSION "${MAJOR_VERSION}.${MINOR_VERSION}")
+SET_TARGET_PROPERTIES(webvi PROPERTIES COMPILE_FLAGS "-fvisibility=hidden")
+SET_TARGET_PROPERTIES(webvistatic PROPERTIES OUTPUT_NAME webvi)
+
+ADD_DEFINITIONS(-DLIBWEBVI_LOG_DOMAIN="libwebvi")
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall")
+
+# Required libraries
+FIND_PACKAGE(LibXml2 REQUIRED)
+FIND_PACKAGE(CURL REQUIRED)
+FIND_PACKAGE(LibTidy REQUIRED)
+
+FIND_PACKAGE(PkgConfig)
+PKG_CHECK_MODULES(GLIB REQUIRED glib-2.0)
+ADD_DEFINITIONS(${GLIB_CFLAGS} ${GLIB_CFLAGS_OTHER})
+LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
+
+INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR})
+INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIRS})
+INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
+INCLUDE_DIRECTORIES(${LIBTIDY_INCLUDE_DIRS})
+
+ADD_DEFINITIONS(-DHAVE_TIDY_ULONG_VERSION=${HAVE_TIDY_ULONG_VERSION})
+
+TARGET_LINK_LIBRARIES(webvi ${GLIB_LIBRARIES} ${LIBXML2_LIBRARIES} ${CURL_LIBRARIES} ${LIBTIDY_LIBRARIES})
+
+# Installing
+INSTALL(TARGETS webvi DESTINATION bin)
+INSTALL(FILES libwebvi.h DESTINATION include)
diff --git a/src/libwebvi/libwebvi.c b/src/libwebvi/libwebvi.c
new file mode 100644
index 0000000..b3d030a
--- /dev/null
+++ b/src/libwebvi/libwebvi.c
@@ -0,0 +1,338 @@
+/*
+ * libwebvi.c
+ *
+ * Copyright (c) 2010-2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <libxml/xmlversion.h>
+#include "libwebvi.h"
+#include "webvicontext.h"
+#include "request.h"
+#include "version.h"
+
+static const char *VERSION = "libwebvi/" LIBWEBVI_VERSION;
+
+struct WebviErrorMessage {
+ WebviResult code;
+ const char *message;
+};
+
+int webvi_global_init() {
+ LIBXML_TEST_VERSION
+ return 0;
+}
+
+void webvi_cleanup() {
+ webvi_context_cleanup_all();
+}
+
+WebviCtx webvi_initialize_context(void) {
+ return webvi_context_initialize();
+}
+
+void webvi_cleanup_context(WebviCtx ctxhandle) {
+ webvi_context_cleanup(ctxhandle);
+}
+
+const char* webvi_version(void) {
+ return VERSION;
+}
+
+const char* webvi_strerror(WebviResult err) {
+ static struct WebviErrorMessage error_messages[] =
+ {{WEBVIERR_OK, "Succeeded"},
+ {WEBVIERR_INVALID_HANDLE, "Invalid handle"},
+ {WEBVIERR_INVALID_PARAMETER, "Invalid parameter"},
+ {WEBVIERR_UNKNOWN_ERROR, "Internal error"}};
+
+ for (int i=0; i<(sizeof(error_messages)/sizeof(error_messages[0])); i++) {
+ if (err == error_messages[i].code) {
+ return error_messages[i].message;
+ }
+ }
+
+ return "Internal error";
+}
+
+WebviResult webvi_set_config(WebviCtx ctxhandle, WebviConfig conf, ...) {
+ va_list argptr;
+ const char *p;
+ WebviResult res = WEBVIERR_OK;
+
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ va_start(argptr, conf);
+
+ switch (conf) {
+ case WEBVI_CONFIG_TEMPLATE_PATH:
+ p = va_arg(argptr, char *);
+ webvi_context_set_template_path(ctx, p);
+ break;
+ case WEBVI_CONFIG_DEBUG:
+ p = va_arg(argptr, char *);
+ webvi_context_set_debug(ctx, strcmp(p, "0") != 0);
+ break;
+ case WEBVI_CONFIG_TIMEOUT_CALLBACK:
+ // FIXME
+ // va_arg(argptr, long)
+ break;
+ case WEBVI_CONFIG_TIMEOUT_DATA:
+ // FIXME
+ // va_arg(argptr, long)
+ break;
+ default:
+ res = WEBVIERR_INVALID_PARAMETER;
+ };
+
+ va_end(argptr);
+
+ return res;
+}
+
+WebviHandle webvi_new_request(WebviCtx ctxhandle, const char *href) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVI_INVALID_HANDLE;
+
+ WebviRequest *req = request_create(href, ctx);
+ return webvi_context_add_request(ctx, req);
+}
+
+WebviResult webvi_start_request(WebviCtx ctxhandle, WebviHandle h) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ WebviRequest *req = webvi_context_get_request(ctx, h);
+ if (!req)
+ return WEBVIERR_INVALID_HANDLE;
+
+ request_start(req);
+
+ return WEBVIERR_OK;
+}
+
+WebviResult webvi_stop_request(WebviCtx ctxhandle, WebviHandle h) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ WebviRequest *req = webvi_context_get_request(ctx, h);
+ if (!req)
+ return WEBVIERR_INVALID_HANDLE;
+
+ request_stop(req);
+
+ return WEBVIERR_OK;
+}
+
+WebviResult webvi_delete_request(WebviCtx ctxhandle, WebviHandle h) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ WebviResult res = webvi_stop_request(ctxhandle, h);
+ if (res != WEBVIERR_OK)
+ return res;
+
+ webvi_context_remove_request(ctx, h);
+
+ return WEBVIERR_OK;
+}
+
+WebviResult webvi_set_opt(WebviCtx ctxhandle, WebviHandle h, WebviOption opt, ...) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ WebviRequest *req = webvi_context_get_request(ctx, h);
+ if (!req)
+ return WEBVIERR_INVALID_HANDLE;
+
+ va_list argptr;
+ WebviResult res = WEBVIERR_OK;
+
+ va_start(argptr, opt);
+
+ switch (opt) {
+ case WEBVIOPT_WRITEFUNC:
+ request_set_write_callback(req, va_arg(argptr, webvi_callback));
+ break;
+ case WEBVIOPT_READFUNC:
+ request_set_read_callback(req, va_arg(argptr, webvi_callback));
+ break;
+ case WEBVIOPT_WRITEDATA:
+ request_set_write_data(req, va_arg(argptr, void *));
+ break;
+ case WEBVIOPT_READDATA:
+ request_set_read_data(req, va_arg(argptr, void *));
+ break;
+ default:
+ res = WEBVIERR_INVALID_PARAMETER;
+ };
+
+ va_end(argptr);
+
+ return res;
+}
+
+WebviResult webvi_get_info(WebviCtx ctxhandle, WebviHandle h, WebviInfo info, ...) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ WebviRequest *req = webvi_context_get_request(ctx, h);
+ if (!req)
+ return WEBVIERR_INVALID_HANDLE;
+
+ va_list argptr;
+ WebviResult res = WEBVIERR_OK;
+
+ va_start(argptr, info);
+
+ switch (info) {
+ case WEBVIINFO_URL:
+ {
+ char **output = va_arg(argptr, char **);
+ if (output) {
+ *output = NULL;
+
+ const char *url = request_get_url(req);
+ if (url) {
+ *output = malloc(strlen(url)+1);
+ if (*output) {
+ strcpy(*output, url);
+ }
+ }
+ }
+ break;
+ }
+
+ case WEBVIINFO_CONTENT_LENGTH:
+ {
+ // FIXME
+ long *content_length = va_arg(argptr, long *);
+ if (content_length)
+ *content_length = -1;
+ break;
+ }
+
+ case WEBVIINFO_CONTENT_TYPE:
+ {
+ // FIXME
+ char **output = va_arg(argptr, char **);
+ if (output) {
+ *output = malloc(1);
+ **output = '\0';
+ }
+ break;
+ }
+
+ case WEBVIINFO_STREAM_TITLE:
+ {
+ // FIXME
+ char **output = va_arg(argptr, char **);
+ if (output) {
+ *output = malloc(1);
+ **output = '\0';
+ }
+ break;
+ }
+
+ default:
+ res = WEBVIERR_INVALID_PARAMETER;
+ };
+
+ va_end(argptr);
+
+ return res;
+}
+
+WebviResult webvi_fdset(WebviCtx ctxhandle, fd_set *readfd, fd_set *writefd,
+ fd_set *excfd, int *max_fd) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ return webvi_context_fdset(ctx, readfd, writefd, excfd, max_fd);
+}
+
+WebviResult webvi_perform(WebviCtx ctxhandle, int sockfd, int ev_bitmask,
+ long *running_handles) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return WEBVIERR_INVALID_HANDLE;
+
+ webvi_context_handle_socket_action(ctx, sockfd, ev_bitmask, running_handles);
+
+ return WEBVIERR_OK;
+}
+
+WebviMsg *webvi_get_message(WebviCtx ctxhandle, int *remaining_messages) {
+ WebviContext *ctx = get_context_by_handle(ctxhandle);
+ if (!ctx)
+ return NULL;
+
+ return webvi_context_next_message(ctx, remaining_messages);
+}
+
+int webvi_process_some(WebviCtx ctx, int timeout_seconds) {
+ fd_set readfds;
+ fd_set writefds;
+ fd_set excfds;
+ int maxfd;
+ int s;
+ WebviResult res;
+ struct timeval timeout;
+ long running_handles;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&excfds);
+ res = webvi_fdset(ctx, &readfds, &writefds, &excfds, &maxfd);
+ if (res != WEBVIERR_OK) {
+ return -1;
+ }
+
+ timeout.tv_sec = timeout_seconds;
+ timeout.tv_usec = 0;
+ s = select(maxfd+1, &readfds, &writefds, NULL, &timeout);
+
+ if (s == -1) {
+ // error
+ return -1;
+ } else if (s == 0) {
+ // timeout
+ webvi_perform(ctx, WEBVI_SELECT_TIMEOUT, WEBVI_SELECT_CHECK, &running_handles);
+ } else {
+ // handle one fd
+ for (int fd=0; fd<=maxfd; fd++) {
+ if (FD_ISSET(fd, &readfds)) {
+ webvi_perform(ctx, fd, WEBVI_SELECT_READ, &running_handles);
+ } else if (FD_ISSET(fd, &writefds)) {
+ webvi_perform(ctx, fd, WEBVI_SELECT_WRITE, &running_handles);
+ } else if (FD_ISSET(fd, &excfds)) {
+ webvi_perform(ctx, fd, WEBVI_SELECT_EXCEPTION, &running_handles);
+ }
+ }
+ }
+
+ return running_handles;
+}
diff --git a/src/libwebvi/libwebvi.h b/src/libwebvi/libwebvi.h
new file mode 100644
index 0000000..8efe953
--- /dev/null
+++ b/src/libwebvi/libwebvi.h
@@ -0,0 +1,370 @@
+/*
+ * libwebvi.h: C bindings for webvi Python module
+ *
+ * Copyright (c) 2010-2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBWEBVI_H
+#define __LIBWEBVI_H
+
+#include <sys/select.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if webvi_EXPORTS /* defined when building as shared library */
+ #if defined _WIN32 || defined __CYGWIN__
+ #define LIBWEBVI_DLL_EXPORT __declspec(dllexport)
+ #else
+ #if __GNUC__ >= 4
+ #define LIBWEBVI_DLL_EXPORT __attribute__((__visibility__("default")))
+ #endif
+ #endif
+#endif
+
+#ifndef LIBWEBVI_DLL_EXPORT
+#define LIBWEBVI_DLL_EXPORT
+#endif
+
+typedef int WebviHandle;
+typedef long WebviCtx;
+
+typedef ssize_t (*webvi_callback)(const char *, size_t, void *);
+typedef void (*webvi_timeout_callback)(long, void *);
+
+#define WEBVI_INVALID_HANDLE -1
+
+typedef enum {
+ WEBVIMSG_DONE
+} WebviMsgType;
+
+typedef enum {
+ WEBVISTATE_NOT_FINISHED = 0,
+ WEBVISTATE_FINISHED_OK = 1,
+ WEBVISTATE_MEMORY_ALLOCATION_ERROR = 2,
+ WEBVISTATE_NOT_FOUND = 3,
+ WEBVISTATE_NETWORK_READ_ERROR = 4,
+ WEBVISTATE_IO_ERROR = 5,
+ WEBVISTATE_TIMEDOUT = 6,
+ WEBVISTATE_SUBPROCESS_FAILED = 7,
+ WEBVISTATE_INTERNAL_ERROR = 999,
+} RequestState;
+
+typedef enum {
+ WEBVIREQ_MENU,
+ WEBVIREQ_FILE,
+ WEBVIREQ_STREAMURL
+} WebviRequestType;
+
+typedef enum {
+ WEBVIERR_UNKNOWN_ERROR = -1,
+ WEBVIERR_OK = 0,
+ WEBVIERR_INVALID_HANDLE,
+ WEBVIERR_INVALID_PARAMETER
+} WebviResult;
+
+typedef enum {
+ WEBVIOPT_WRITEFUNC,
+ WEBVIOPT_READFUNC,
+ WEBVIOPT_WRITEDATA,
+ WEBVIOPT_READDATA,
+} WebviOption;
+
+typedef enum {
+ WEBVIINFO_URL,
+ WEBVIINFO_CONTENT_LENGTH,
+ WEBVIINFO_CONTENT_TYPE,
+ WEBVIINFO_STREAM_TITLE
+} WebviInfo;
+
+#define WEBVI_SELECT_TIMEOUT -1
+
+typedef enum {
+ WEBVI_SELECT_CHECK = 0,
+ WEBVI_SELECT_READ = 1,
+ WEBVI_SELECT_WRITE = 2,
+ WEBVI_SELECT_EXCEPTION = 4
+} WebviSelectBitmask;
+
+typedef enum {
+ WEBVI_CONFIG_TEMPLATE_PATH,
+ WEBVI_CONFIG_DEBUG,
+ WEBVI_CONFIG_TIMEOUT_CALLBACK,
+ WEBVI_CONFIG_TIMEOUT_DATA
+} WebviConfig;
+
+typedef struct {
+ WebviMsgType msg;
+ WebviHandle handle;
+ RequestState status_code;
+ const char *data;
+} WebviMsg;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Initialize the library. Must be called before any other functions
+ * (the only exception is webvi_version() which can be called before
+ * the library is initialized).
+ *
+ * Returns 0, if initialization succeeds.
+ */
+LIBWEBVI_DLL_EXPORT int webvi_global_init(void);
+
+/*
+ * Frees all resources currently used by libwebvi and terminates all
+ * active connections. Do not call any libwebvi function after this.
+ */
+LIBWEBVI_DLL_EXPORT void webvi_cleanup();
+
+/*
+ * Create a new context. A valid context is required for calling other
+ * functions in the library. The created contextes are independent of
+ * each other. The context must be destroyed by a call to
+ * webvi_cleanup_context when no longer needed.
+ *
+ * Return value 0 indicates an error.
+ */
+LIBWEBVI_DLL_EXPORT WebviCtx webvi_initialize_context(void);
+
+/*
+ * Free resources allocated by context ctx. The context can not be
+ * used anymore after a call to this function.
+ */
+LIBWEBVI_DLL_EXPORT void webvi_cleanup_context(WebviCtx ctx);
+
+/*
+ * Return the version of libwebvi as a string. The returned value
+ * points to a static buffer, and the caller should modify or not free() it.
+ */
+LIBWEBVI_DLL_EXPORT const char* webvi_version(void);
+
+/*
+ * Return a string describing an error code. The returned value points
+ * to a read-only buffer, and the caller should not modify or free() it.
+ */
+LIBWEBVI_DLL_EXPORT const char* webvi_strerror(WebviResult err);
+
+/*
+ * Set a new value for a global configuration option conf.
+ *
+ * Possible values and their meanings:
+ *
+ * WEBVI_CONFIG_TEMPLATE_PATH
+ * Set the base directory for the XSLT templates (char *)
+ *
+ * WEBVI_CONFIG_DEBUG
+ * If value is not "0", print debug output to stdin (char *)
+ *
+ * WEBVI_CONFIG_TIMEOUT_CALLBACK
+ * Set timeout callback function (webvi_timeout_callback)
+ *
+ * WEBVI_CONFIG_TIMEOUT_DATA
+ * Set user data which will passed as second argument of the timeout
+ * callback (void *)
+ *
+ * The strings (char * arguments) are copied to the library (the user
+ * can free their original copy).
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_set_config(WebviCtx ctx, WebviConfig conf, ...);
+
+/*
+ * Creates a new download request.
+ *
+ * href is a URI of the resource that should be downloaded. Typically,
+ * the reference has been acquired from a previously downloaded menu.
+ * A special constant "wvt://mainmenu" can be used to download the
+ * mainmenu.
+ *
+ * The return value is a handle to the newly created request. Value
+ * WEBVI_INVALID_HANDLE indicates an error.
+ *
+ * The request is initialized but the actual network transfer is not
+ * started. You can set up additional configuration options on the
+ * handle using webvi_set_opt() before starting the handle with
+ * webvi_start_handle().
+ */
+LIBWEBVI_DLL_EXPORT WebviHandle webvi_new_request(WebviCtx ctx, const char *href);
+
+/*
+ * Starts the transfer on request h. The transfer one or more sockets
+ * whose file descriptors are returned by webvi_fdset(). The actual
+ * transfer is done during webvi_perform() calls.
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_start_request(WebviCtx ctx, WebviHandle h);
+
+/*
+ * Requests that the transfer on request h shoud be aborted. After the
+ * library has actually finished aborting the transfer, the handle h
+ * is returned by webvi_get_message() with non-zero status code.
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_stop_request(WebviCtx ctx, WebviHandle h);
+
+/*
+ * Frees resources associated with request h. The handle can not be
+ * used after this call. If the handle is still in the middle of a
+ * transfer, the transfer is forcefully aborted.
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_delete_request(WebviCtx ctx, WebviHandle h);
+
+/*
+ * Sets configuration options that changes behaviour of the handle.
+ * opt is one of the values of WebviOption enum as indicated below.
+ * The fourth parameter sets the value of the specified option. Its
+ * type depends on opt as discussed below.
+ *
+ * Possible values for opt:
+ *
+ * WEBVIOPT_WRITEFUNC
+ *
+ * Set the callback function that shall be called when data is read
+ * from the network. The fourth parameter is a pointer to the callback
+ * function
+ *
+ * ssize_t (*webvi_callback)(const char *, size_t, void *).
+ *
+ * When the function is called, the first parameter is a pointer to
+ * the incoming data, the second parameters is the size of the
+ * incoming data block in bytes, and the third parameter is a pointer
+ * to user's data structure can be set by WEBVIOPT_WRITEDATA option.
+ *
+ * The callback function should return the number of bytes is
+ * processed. If this differs from the size of the incoming data
+ * block, it indicates that an error occurred and the transfer will be
+ * aborted.
+ *
+ * If write callback has not been set (or if it is set to NULL) the
+ * incoming data is printed to stdout.
+ *
+ * WEBVIOPT_WRITEDATA
+ *
+ * Sets the value that will be passed to the write callback. The
+ * fourth parameter is of type void *.
+ *
+ * WEBVIOPT_READFUNC
+ *
+ * Set the callback function that shall be called when data is to be
+ * send to network. The fourth parameter is a pointer to the callback
+ * function
+ *
+ * ssize_t (*webvi_callback)(const char *, size_t, void *)
+ *
+ * The first parameter is a pointer to a buffer where the data that is
+ * to be sent should be written. The second parameter is the maximum
+ * size of the buffer. The thirs parameter is a pointer to user data
+ * set with WEBVIOPT_READDATA.
+ *
+ * The return value should be the number of bytes actually written to
+ * the buffer. If the return value is -1, the transfer is aborted.
+ *
+ * WEBVIOPT_READDATA
+ *
+ * Sets the value that will be passed to the read callback. The
+ * fourth parameter is of type void *.
+ *
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_set_opt(WebviCtx ctx, WebviHandle h, WebviOption opt, ...);
+
+/*
+ * Get information specific to a WebviHandle. The value will be
+ * written to the memory location pointed by the third argument. The
+ * type of the pointer depends in the second parameter as discused
+ * below.
+ *
+ * Available information:
+ *
+ * WEBVIINFO_URL
+ *
+ * Receive URL. The third parameter must be a pointer to char *. The
+ * caller must free() the memory.
+ *
+ * WEBVIINFO_CONTENT_LENGTH
+ *
+ * Receive the value of Content-length field, or -1 if the size is
+ * unknown. The third parameter must be a pointer to long.
+ *
+ * WEBVIINFO_CONTENT_TYPE
+ *
+ * Receive the Content-type string. The returned value is NULL, if the
+ * Content-type is unknown. The third parameter must be a pointer to
+ * char *. The caller must free() the memory.
+ *
+ * WEBVIINFO_STREAM_TITLE
+ *
+ * Receive stream title. The returned value is NULL, if title is
+ * unknown. The third parameter must be a pointer to char *. The
+ * caller must free() the memory.
+ *
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_get_info(WebviCtx ctx, WebviHandle h, WebviInfo info, ...);
+
+/*
+ * Get active file descriptors in use by the library. The file
+ * descriptors that should be waited for reading, writing or
+ * exceptions are returned in read_fd_set, write_fd_set and
+ * exc_fd_set, respectively. The fd_sets are not cleared, but the new
+ * file descriptors are added to them. max_fd will contain the highest
+ * numbered file descriptor that was returned in one of the fd_sets.
+ *
+ * One should wait for action in one of the file descriptors returned
+ * by this function using select(), poll() or similar system call,
+ * and, after seeing action on a file descriptor, call webvi_perform
+ * on that descriptor.
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_fdset(WebviCtx ctx, fd_set *readfd, fd_set *writefd, fd_set *excfd, int *max_fd);
+
+/*
+ * Perform input or output action on a file descriptor.
+ *
+ * activefd is a file descriptor that was returned by an earlier call
+ * to webvi_fdset and has been signalled to be ready by select() or
+ * similar function. ev_bitmask should be OR'ed combination of
+ * WEBVI_SELECT_READ, WEBVI_SELECT_WRITE, WEBVI_SELECT_EXCEPTION to
+ * indicate that activefd has been signalled to be ready for reading,
+ * writing or being in exception state, respectively. ev_bitmask can
+ * also set to WEBVI_SELECT_CHECK which means that the state is
+ * checked internally. On return, running_handles will contain the
+ * number of still active file descriptors.
+ *
+ * If a timeout occurs before any file descriptor becomes ready, this
+ * function should be called with sockfd set to WEBVI_SELECT_TIMEOUT
+ * and ev_bitmask set to WEBVI_SELECT_CHECK.
+ */
+LIBWEBVI_DLL_EXPORT WebviResult webvi_perform(WebviCtx ctx, int sockfd, int ev_bitmask, long *running_handles);
+
+
+LIBWEBVI_DLL_EXPORT int webvi_process_some(WebviCtx ctx, int timeout_seconds);
+
+
+/*
+ * Return the next message from the message queue. Currently the only
+ * message, WEBVIMSG_DONE, indicates that a transfer on a handle has
+ * finished. The number of messages remaining in the queue after this
+ * call is written to remaining_messages. The pointers in the returned
+ * WebviMsg point to handle's internal buffers and is valid until the
+ * next call to webvi_get_message(). The caller should not free the
+ * returned WebviMsg. The return value is NULL if there is no messages
+ * in the queue.
+ */
+LIBWEBVI_DLL_EXPORT WebviMsg *webvi_get_message(WebviCtx ctx, int *remaining_messages);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/src/libwebvi/link.c b/src/libwebvi/link.c
new file mode 100644
index 0000000..501e70a
--- /dev/null
+++ b/src/libwebvi/link.c
@@ -0,0 +1,86 @@
+#include <stdlib.h>
+#include <glib.h>
+#include "link.h"
+
+struct Link {
+ gchar *href;
+ gchar *title;
+ LinkActionType type;
+};
+
+struct LinkAction {
+ LinkActionType type;
+ gchar *command;
+};
+
+struct Link *link_create(const char *href, const char *title, LinkActionType type) {
+ struct Link *self = malloc(sizeof(struct Link));
+ self->href = g_strdup(href ? href : "");
+ self->title = g_strdup(title ? title : "");
+ self->type = type;
+ return self;
+}
+
+const char *link_get_href(const struct Link *self) {
+ return self->href;
+}
+
+const char *link_get_title(const struct Link *self) {
+ return self->title;
+}
+
+LinkActionType link_get_type(const struct Link *self) {
+ return self->type;
+}
+
+void link_delete(struct Link *self) {
+ g_free(self->href);
+ g_free(self->title);
+ free(self);
+}
+
+void g_free_link(gpointer data) {
+ link_delete((struct Link *)data);
+}
+
+struct LinkAction *link_action_create(LinkActionType type, const char *command) {
+ struct LinkAction *self = malloc(sizeof(struct LinkAction));
+ self->type = type;
+ self->command = g_strdup(command ? command : "");
+ return self;
+}
+
+LinkActionType link_action_get_type(const struct LinkAction *self) {
+ return self->type;
+}
+
+const char *link_action_get_command(const struct LinkAction *self) {
+ return self->command;
+}
+
+void link_action_delete(struct LinkAction *self) {
+ if (self) {
+ g_free(self->command);
+ free(self);
+ }
+}
+
+struct ActionTypeMessage {
+ LinkActionType type;
+ const char *message;
+};
+
+const char *link_action_type_to_string(LinkActionType atype) {
+ static struct ActionTypeMessage messages[] =
+ {{LINK_ACTION_PARSE, "regular link"},
+ {LINK_ACTION_STREAM_LIBQUVI, "stream"},
+ {LINK_ACTION_EXTERNAL_COMMAND, "external command"}};
+
+ for (int i=0; i<(sizeof(messages)/sizeof(messages[0])); i++) {
+ if (atype == messages[i].type) {
+ return messages[i].message;
+ }
+ }
+
+ return "???";
+}
diff --git a/src/libwebvi/link.h b/src/libwebvi/link.h
new file mode 100644
index 0000000..0ddc05c
--- /dev/null
+++ b/src/libwebvi/link.h
@@ -0,0 +1,45 @@
+/*
+ * link.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINK_H
+#define __LINK_H
+
+typedef enum {
+ LINK_ACTION_PARSE,
+ LINK_ACTION_STREAM_LIBQUVI,
+ LINK_ACTION_EXTERNAL_COMMAND
+} LinkActionType;
+
+typedef struct Link Link;
+typedef struct LinkAction LinkAction;
+
+struct Link *link_create(const char *href, const char *title, LinkActionType type);
+const char *link_get_href(const struct Link *self);
+const char *link_get_title(const struct Link *self);
+LinkActionType link_get_type(const struct Link *self);
+void link_delete(struct Link *self);
+void g_free_link(gpointer link);
+
+struct LinkAction *link_action_create(LinkActionType type, const char *command);
+LinkActionType link_action_get_type(const struct LinkAction *self);
+const char *link_action_get_command(const struct LinkAction *self);
+void link_action_delete(struct LinkAction *self);
+const char *link_action_type_to_string(LinkActionType atype);
+
+#endif // __LINK_H
diff --git a/src/libwebvi/linkextractor.c b/src/libwebvi/linkextractor.c
new file mode 100644
index 0000000..d683df6
--- /dev/null
+++ b/src/libwebvi/linkextractor.c
@@ -0,0 +1,138 @@
+#include <string.h>
+#ifdef HAVE_TIDY_ULONG_VERSION
+#define __USE_MISC
+#include <sys/types.h>
+#undef __USE_MISC
+#endif
+#include <tidy/tidy.h>
+#include <tidy/buffio.h>
+#include "linkextractor.h"
+#include "urlutils.h"
+
+#define MENU_HEADER "<?xml version=\"1.0\"?><wvmenu>"
+#define MENU_FOOTER "</wvmenu>"
+
+struct LinkExtractor {
+ const LinkTemplates *link_templates;
+ TidyBuffer html_buffer;
+ gchar *baseurl;
+};
+
+static GPtrArray *extract_links(const LinkExtractor *self, TidyDoc tdoc);
+static void free_link(gpointer p);
+static void get_links_recursively(TidyDoc tdoc,
+ TidyNode node,
+ const LinkTemplates *link_templates,
+ const gchar *baseurl,
+ GPtrArray *links_found);
+static void getTextContent(TidyDoc tdoc, TidyNode node, TidyBuffer* buf);
+
+LinkExtractor *link_extractor_create(const LinkTemplates *link_templates, const gchar *baseurl) {
+ LinkExtractor *extractor;
+ extractor = malloc(sizeof(LinkExtractor));
+ memset(extractor, 0, sizeof(LinkExtractor));
+ extractor->link_templates = link_templates;
+ tidyBufInit(&extractor->html_buffer);
+ extractor->baseurl = baseurl ? g_strdup(baseurl) : g_strdup("");
+ return extractor;
+}
+
+void link_extractor_delete(LinkExtractor *self) {
+ if (self) {
+ tidyBufFree(&self->html_buffer);
+ g_free(self->baseurl);
+ free(self);
+ }
+}
+
+void link_extractor_append(LinkExtractor *self, const char *buf, size_t len) {
+ tidyBufAppend(&self->html_buffer, (void *)buf, len);
+}
+
+GPtrArray *link_extractor_get_links(LinkExtractor *self) {
+ GPtrArray *links = NULL;
+ TidyDoc tdoc;
+ int err;
+ TidyBuffer errbuf; // swallow errors here instead of printing to stderr
+
+ tdoc = tidyCreate();
+ tidyOptSetBool(tdoc, TidyForceOutput, yes);
+ tidyOptSetInt(tdoc, TidyWrapLen, 4096);
+ tidyBufInit(&errbuf);
+ tidySetErrorBuffer(tdoc, &errbuf);
+
+ err = tidyParseBuffer(tdoc, &self->html_buffer);
+ if (err >= 0) {
+ err = tidyCleanAndRepair(tdoc);
+ if ( err >= 0 ) {
+ links = extract_links(self, tdoc);
+ }
+ }
+
+ tidyBufFree(&errbuf);
+ tidyRelease(tdoc);
+
+ return links;
+}
+
+GPtrArray *extract_links(const LinkExtractor *self, TidyDoc tdoc) {
+ GPtrArray *links = g_ptr_array_new_full(0, free_link);
+ TidyNode root = tidyGetBody(tdoc);
+ get_links_recursively(tdoc, root, self->link_templates, self->baseurl, links);
+ return links;
+}
+
+void get_links_recursively(TidyDoc tdoc, TidyNode node,
+ const LinkTemplates *link_templates,
+ const gchar *baseurl,
+ GPtrArray *links_found) {
+ TidyNode child;
+ for (child = tidyGetChild(node); child; child = tidyGetNext(child)) {
+ if (tidyNodeIsA(child)) {
+ TidyAttr href_attr = tidyAttrGetById(child, TidyAttr_HREF);
+ ctmbstr href = tidyAttrValue(href_attr);
+ if (href && *href != '\0' && href[strlen(href)-1] != '#') {
+ gchar *absolute_href = relative_url_to_absolute(baseurl, href);
+ const LinkAction *action = \
+ link_templates_get_action(link_templates, absolute_href);
+ if (action) {
+ TidyBuffer titlebuf;
+ tidyBufInit(&titlebuf);
+ getTextContent(tdoc, child, &titlebuf);
+ tidyBufPutByte(&titlebuf, '\0');
+ gchar *title = g_strdup((const gchar*)titlebuf.bp);
+ g_strstrip(title);
+ LinkActionType type = link_action_get_type(action);
+ Link *link = link_create(absolute_href, title, type);
+ g_ptr_array_add(links_found, link);
+ g_free(title);
+ tidyBufFree(&titlebuf);
+ }
+ g_free(absolute_href);
+ }
+ } else {
+ TidyNodeType node_type = tidyNodeGetType(node);
+ if (node_type == TidyNode_Root || node_type == TidyNode_Start) {
+ get_links_recursively(tdoc, child, link_templates, baseurl, links_found);
+ }
+ }
+ }
+}
+
+void getTextContent(TidyDoc tdoc, TidyNode node, TidyBuffer* buf) {
+ if (tidyNodeGetType(node) == TidyNode_Text) {
+ TidyBuffer content;
+ tidyBufInit(&content);
+ tidyNodeGetValue(tdoc, node, &content);
+ tidyBufAppend(buf, content.bp, content.size);
+ } else {
+ TidyNode child;
+ for (child = tidyGetChild(node); child; child = tidyGetNext(child)) {
+ getTextContent(tdoc, child, buf);
+ }
+ }
+}
+
+void free_link(gpointer p) {
+ link_delete((Link *)p);
+}
diff --git a/src/libwebvi/linkextractor.h b/src/libwebvi/linkextractor.h
new file mode 100644
index 0000000..770b62f
--- /dev/null
+++ b/src/libwebvi/linkextractor.h
@@ -0,0 +1,34 @@
+/*
+ * linkextractor.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINKEXTRACTOR_H
+#define __LINKEXTRACTOR_H
+
+#include <glib.h>
+#include "linktemplates.h"
+
+typedef struct LinkExtractor LinkExtractor;
+
+LinkExtractor *link_extractor_create(const LinkTemplates *link_templates,
+ const gchar *baseurl);
+void link_extractor_delete(LinkExtractor *link_extractor);
+void link_extractor_append(LinkExtractor *link_extractor, const char *buf, size_t len);
+GPtrArray *link_extractor_get_links(LinkExtractor *link_extractor);
+
+#endif // __LINKEXTRACTOR_H
diff --git a/src/libwebvi/linktemplates.c b/src/libwebvi/linktemplates.c
new file mode 100644
index 0000000..a193df3
--- /dev/null
+++ b/src/libwebvi/linktemplates.c
@@ -0,0 +1,172 @@
+#include <string.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "linktemplates.h"
+#include "link.h"
+
+#define STREAM_LIBQUVI_SELECTOR "stream:libquvi"
+#define STREAM_LIBQUVI_SELECTOR_LEN strlen(STREAM_LIBQUVI_SELECTOR)
+#define STREAM_SELECTOR "stream:"
+#define STREAM_SELECTOR_LEN strlen(STREAM_SELECTOR)
+#define EXT_CMD_SELECTOR "bin:"
+#define EXT_CMD_SELECTOR_LEN strlen(EXT_CMD_SELECTOR)
+
+struct LinkTemplates {
+ GPtrArray *matcher;
+};
+
+struct LinkMatcher {
+ GRegex *pattern;
+ LinkAction *action;
+};
+
+static void free_link_matcher(gpointer link);
+static struct LinkMatcher *parse_line(const char *line);
+static LinkAction *parse_action(const gchar *actionstr);
+
+LinkTemplates *link_templates_create() {
+ LinkTemplates *config = malloc(sizeof(LinkTemplates));
+ if (!config)
+ return NULL;
+ memset(config, 0, sizeof(LinkTemplates));
+ config->matcher = g_ptr_array_new_with_free_func(free_link_matcher);
+ return config;
+}
+
+void link_templates_delete(LinkTemplates *conf) {
+ g_ptr_array_free(conf->matcher, TRUE);
+ free(conf);
+}
+
+void link_templates_load(LinkTemplates *conf, const char *filename) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+ "Loading matchers from %s", filename);
+ FILE *file = fopen(filename, "r");
+ if (!file) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Failed to read file %s", filename);
+ return;
+ }
+
+ char line[1024];
+ while (fgets(line, sizeof line, file)) {
+ struct LinkMatcher *link = parse_line(line);
+ if (link) {
+ g_ptr_array_add(conf->matcher, link);
+ }
+ }
+
+ fclose(file);
+}
+
+struct LinkMatcher *parse_line(const char *line) {
+ if (!line)
+ return NULL;
+
+ const char *p = line;
+ while (*p == ' ')
+ p++;
+
+ if (*p == '\0' || *p == '#' || *p == '\n' || *p == '\r')
+ return NULL;
+
+ const char *end = line + strlen(line);
+ while ((end-1 >= p) &&
+ (end[-1] == ' ' || end[-1] == '\r' || end[-1] == '\n' || end[-1] == '\t'))
+ end--;
+
+ if (end <= p)
+ return NULL;
+
+ const char *tab = memchr(p, '\t', end-p);
+ gchar *pattern;
+ LinkAction *action;
+ if (tab && tab < end) {
+ pattern = g_strndup(p, tab-p);
+ const char *cmdstart = tab+1;
+ gchar *action_field = g_strndup(cmdstart, end-cmdstart);
+ action = parse_action(action_field);
+ g_free(action_field);
+ } else {
+ pattern = g_strndup(p, end-p);
+ action = link_action_create(LINK_ACTION_PARSE, NULL);
+ }
+
+ if (!action) {
+ g_free(pattern);
+ return NULL;
+ }
+
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+ "Compiling pattern %s %s %s", pattern,
+ link_action_type_to_string(link_action_get_type(action)),
+ link_action_get_command(action));
+
+ struct LinkMatcher *matcher = malloc(sizeof(struct LinkMatcher));
+ GError *err = NULL;
+ matcher->pattern = g_regex_new(pattern, G_REGEX_OPTIMIZE, 0, &err);
+ matcher->action = action;
+
+ if (err) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Error compiling pattern %s: %s", pattern, err->message);
+ g_error_free(err);
+ free_link_matcher(matcher);
+ matcher = NULL;
+ }
+
+ g_free(pattern);
+ return matcher;
+}
+
+LinkAction *parse_action(const gchar *actionstr) {
+ if (!actionstr)
+ return NULL;
+
+ if (*actionstr == '\0') {
+ return link_action_create(LINK_ACTION_PARSE, NULL);
+ } else if (strcmp(actionstr, STREAM_LIBQUVI_SELECTOR) == 0) {
+ return link_action_create(LINK_ACTION_STREAM_LIBQUVI, NULL);
+ } else if (strncmp(actionstr, EXT_CMD_SELECTOR, EXT_CMD_SELECTOR_LEN) == 0) {
+ const gchar *command = actionstr + EXT_CMD_SELECTOR_LEN;
+ return link_action_create(LINK_ACTION_EXTERNAL_COMMAND, command);
+ } else if (strncmp(actionstr, STREAM_SELECTOR, STREAM_SELECTOR_LEN) == 0) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Unknown streamer %s in link template file", actionstr);
+ return NULL;
+ } else {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Invalid action %s in link template file", actionstr);
+ return NULL;
+ }
+}
+
+const LinkAction *link_templates_get_action(const LinkTemplates *conf,
+ const char *url)
+{
+ for (int i=0; i<link_templates_size(conf); i++) {
+ struct LinkMatcher *matcher = g_ptr_array_index(conf->matcher, i);
+ if (g_regex_match(matcher->pattern, url, 0, NULL))
+ {
+ return matcher->action;
+ }
+ }
+
+ return NULL;
+}
+
+int link_templates_size(const LinkTemplates *conf) {
+ return (int)conf->matcher->len;
+}
+
+void free_link_matcher(gpointer ptr) {
+ if (ptr) {
+ struct LinkMatcher *matcher = (struct LinkMatcher *)ptr;
+ if (matcher->pattern)
+ g_regex_unref(matcher->pattern);
+ if (matcher->action)
+ link_action_delete(matcher->action);
+ free(matcher);
+ }
+}
diff --git a/src/libwebvi/linktemplates.h b/src/libwebvi/linktemplates.h
new file mode 100644
index 0000000..1c184cc
--- /dev/null
+++ b/src/libwebvi/linktemplates.h
@@ -0,0 +1,34 @@
+/*
+ * linktemplates.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINKTEMPLATES_H
+#define __LINKTEMPLATES_H
+
+#include "link.h"
+
+typedef struct LinkTemplates LinkTemplates;
+
+LinkTemplates *link_templates_create();
+void link_templates_delete(LinkTemplates *conf);
+void link_templates_load(LinkTemplates *conf, const char *filename);
+int link_templates_size(const LinkTemplates *conf);
+const struct LinkAction *link_templates_get_action(const LinkTemplates *conf,
+ const char *url);
+
+#endif // __LINKTEMPLATES_H
diff --git a/src/libwebvi/mainmenu.c b/src/libwebvi/mainmenu.c
new file mode 100644
index 0000000..df08dce
--- /dev/null
+++ b/src/libwebvi/mainmenu.c
@@ -0,0 +1,120 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include "mainmenu.h"
+#include "menubuilder.h"
+
+static GPtrArray *load_websites(const char *path);
+static gint title_cmp(gconstpointer a, gconstpointer b);
+static gchar *get_site_title(gchar *sitemenu, gsize sitemenu_len);
+
+char *build_mainmenu(const char *path) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "building main menu %s", path);
+
+ MenuBuilder *menu_builder = menu_builder_create();
+ menu_builder_set_title(menu_builder, "Select video source");
+
+ GPtrArray *websites = load_websites(path);
+ menu_builder_append_link_list(menu_builder, websites);
+ char *menu = menu_builder_to_string(menu_builder);
+
+ g_ptr_array_free(websites, TRUE);
+ menu_builder_delete(menu_builder);
+ return menu;
+}
+
+/*
+ * Load known websites from the given directory.
+ *
+ * Traverses each subdirectory looking for file called menu.xml. If
+ * found, reads the file to find site title. Returns an array of
+ * titles and wvt references. The caller must call g_ptr_array_free()
+ * on the returned array (the content of the array will be freed
+ * automatically).
+ */
+GPtrArray *load_websites(const char *path) {
+ GPtrArray *websites = g_ptr_array_new_with_free_func(g_free_link);
+
+ GDir *dir = g_dir_open(path, 0, NULL);
+ if (!dir)
+ return websites;
+
+ const gchar *dirname;
+ while ((dirname = g_dir_read_name(dir))) {
+ gchar *menudir = g_strconcat(path, "/", dirname, NULL);
+ if (g_file_test(menudir, G_FILE_TEST_IS_DIR)) {
+ gchar *menufile = g_strconcat(menudir, "/menu.xml", NULL);
+
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "processing website menu %s", menufile);
+
+ gchar *sitemenu = NULL;
+ gsize sitemenu_len;
+ if (g_file_get_contents(menufile, &sitemenu, &sitemenu_len, NULL)) {
+ gchar *title = get_site_title(sitemenu, sitemenu_len);
+ if (!title) {
+ title = g_strdup(dirname);
+ }
+
+ gchar *href = g_strconcat("wvt://", menufile, NULL);
+ Link *menuitem = link_create(href, title, LINK_ACTION_PARSE);
+ g_ptr_array_add(websites, menuitem);
+ g_free(href);
+ g_free(title);
+ g_free(sitemenu);
+ }
+
+ g_free(menufile);
+ }
+
+ g_free(menudir);
+ }
+
+ g_dir_close(dir);
+
+ g_ptr_array_sort(websites, title_cmp);
+
+ return websites;
+}
+
+gint title_cmp(gconstpointer a, gconstpointer b) {
+ // a and b are pointers to Link pointers!
+ Link *link1 = *(Link **)a;
+ Link *link2 = *(Link **)b;
+
+ return g_ascii_strcasecmp(link_get_title(link1),
+ link_get_title(link2));
+}
+
+/*
+ * Parse the contents of website menu.xml and return site's title.
+ */
+gchar *get_site_title(gchar *menuxml, gsize menuxml_len) {
+ gchar *title = NULL;
+ xmlDocPtr doc = xmlReadMemory(menuxml, menuxml_len, "", NULL,
+ XML_PARSE_NOWARNING | XML_PARSE_NONET);
+ if (!doc)
+ return NULL;
+
+ xmlNode *root = xmlDocGetRootElement(doc);
+ xmlNode *node = root->children;
+ while (node) {
+ if (node->type == XML_ELEMENT_NODE &&
+ xmlStrEqual(node->name, BAD_CAST "title")) {
+ xmlChar *xmltitle = xmlNodeGetContent(node);
+ if (xmltitle) {
+ title = g_strdup((gchar *)xmltitle);
+ xmlFree(xmltitle);
+
+ break;
+ }
+ }
+
+ node = node->next;
+ }
+
+ xmlFreeDoc(doc);
+
+ return title;
+}
diff --git a/src/libwebvi/mainmenu.h b/src/libwebvi/mainmenu.h
new file mode 100644
index 0000000..b350a5f
--- /dev/null
+++ b/src/libwebvi/mainmenu.h
@@ -0,0 +1,27 @@
+/*
+ * mainmenu.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MAINMENU_H
+#define __MAINMENU_H
+
+#include <glib.h>
+
+char *build_mainmenu(const char *path);
+
+#endif // __MAINMENU_H
diff --git a/src/libwebvi/menubuilder.c b/src/libwebvi/menubuilder.c
new file mode 100644
index 0000000..d2b54ce
--- /dev/null
+++ b/src/libwebvi/menubuilder.c
@@ -0,0 +1,99 @@
+#include <stdlib.h>
+#include <string.h>
+#include <libxml/tree.h>
+#include "menubuilder.h"
+
+struct MenuBuilder {
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr ul_node;
+ xmlNodePtr title_node;
+};
+
+static void add_link_to_menu(gpointer data, gpointer instance);
+
+MenuBuilder *menu_builder_create() {
+ MenuBuilder *self = malloc(sizeof(MenuBuilder));
+ if (!self)
+ return NULL;
+ self->doc = xmlNewDoc(BAD_CAST "1.0");
+ self->root = xmlNewNode(NULL, BAD_CAST "wvmenu");
+ xmlDocSetRootElement(self->doc, self->root);
+ self->ul_node = xmlNewNode(NULL, BAD_CAST "ul");
+ xmlAddChild(self->root, self->ul_node);
+ self->title_node = NULL;
+ return self;
+}
+
+void menu_builder_set_title(MenuBuilder *self, const char *title) {
+ if (self->title_node) {
+ xmlUnlinkNode(self->title_node);
+ xmlFreeNode(self->title_node);
+ self->title_node = NULL;
+ }
+
+ self->title_node = xmlNewNode(NULL, BAD_CAST "title");
+ xmlNodeAddContent(self->title_node, BAD_CAST title);
+
+ if (self->root->children) {
+ xmlAddPrevSibling(self->root->children, self->title_node);
+ } else {
+ xmlAddChild(self->root, self->title_node);
+ }
+}
+
+char *menu_builder_to_string(MenuBuilder *self) {
+ xmlChar *buf;
+ int buflen;
+ char *menu;
+
+ xmlDocDumpMemoryEnc(self->doc, &buf, &buflen, "UTF-8");
+ menu = malloc(buflen+1);
+ strcpy(menu, (const char *)buf);
+ xmlFree(buf);
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Menu:\n%s", menu);
+ return menu;
+}
+
+void menu_builder_append_link_list(MenuBuilder *self, GPtrArray *links) {
+ g_ptr_array_foreach(links, add_link_to_menu, self);
+}
+
+void add_link_to_menu(gpointer data, gpointer instance) {
+ MenuBuilder *menubuilder = (MenuBuilder *)instance;
+ Link *link = (Link *)data;
+ menu_builder_append_link(menubuilder, link);
+}
+
+void menu_builder_append_link(MenuBuilder *self, const Link *link) {
+ const char *class;
+ if (link_get_type(link) == LINK_ACTION_STREAM_LIBQUVI) {
+ class = "stream";
+ } else {
+ class = "webvi";
+ }
+ menu_builder_append_link_plain(self, link_get_href(link),
+ link_get_title(link), class);
+}
+
+void menu_builder_append_link_plain(MenuBuilder *self, const char *href,
+ const char *title, const char *class)
+{
+ xmlNodePtr li_node = xmlNewNode(NULL, BAD_CAST "li");
+ xmlNodePtr a_node = xmlNewNode(NULL, BAD_CAST "a");
+ if (title)
+ xmlNodeAddContent(a_node, BAD_CAST title);
+ if (href)
+ xmlNewProp(a_node, BAD_CAST "href", BAD_CAST href);
+ if (class)
+ xmlNewProp(a_node, BAD_CAST "class", BAD_CAST class);
+ xmlAddChild(li_node, a_node);
+ xmlAddChild(self->ul_node, li_node);
+}
+
+void menu_builder_delete(MenuBuilder *self) {
+ if (self) {
+ xmlFreeDoc(self->doc);
+ free(self);
+ }
+}
diff --git a/src/libwebvi/menubuilder.h b/src/libwebvi/menubuilder.h
new file mode 100644
index 0000000..37de6c2
--- /dev/null
+++ b/src/libwebvi/menubuilder.h
@@ -0,0 +1,37 @@
+/*
+ * menubuilder.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MENUBUILDER_H
+#define __MENUBUILDER_H
+
+#include <glib.h>
+#include "link.h"
+
+typedef struct MenuBuilder MenuBuilder;
+
+MenuBuilder *menu_builder_create();
+void menu_builder_set_title(MenuBuilder *self, const char *title);
+char *menu_builder_to_string(MenuBuilder *self);
+void menu_builder_append_link_plain(MenuBuilder *self, const char *href,
+ const char *title, const char *class);
+void menu_builder_append_link(MenuBuilder *self, const Link *link);
+void menu_builder_append_link_list(MenuBuilder *self, GPtrArray *links);
+void menu_builder_delete(MenuBuilder *self);
+
+#endif // __MENUBUILDER_H
diff --git a/src/libwebvi/pipecomponent.c b/src/libwebvi/pipecomponent.c
new file mode 100644
index 0000000..a0743da
--- /dev/null
+++ b/src/libwebvi/pipecomponent.c
@@ -0,0 +1,689 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include "pipecomponent.h"
+#include "menubuilder.h"
+#include "linkextractor.h"
+#include "mainmenu.h"
+#include "webvicontext.h"
+#include "version.h"
+
+#define WEBVID_USER_AGENT "libwebvi/" LIBWEBVI_VERSION " libcurl/" LIBCURL_VERSION
+
+#define INITIALIZE_PIPE(pipetype, process, finish, delete) \
+ pipetype *self = malloc(sizeof(pipetype)); \
+ memset(self, 0, sizeof(pipetype)); \
+ pipe_component_initialize(&self->pipe_data, (process), (finish), (delete))
+
+#define INITIALIZE_PIPE_WITH_FDSET(pipetype, process, finish, delete, fdset, handle_socket) \
+ pipetype *self = malloc(sizeof(pipetype)); \
+ memset(self, 0, sizeof(pipetype)); \
+ pipe_component_initialize_fdset(&self->pipe_data, (process), (finish), (delete), (fdset), (handle_socket))
+
+struct PipeDownloader {
+ PipeComponent pipe_data;
+ CURL *curl;
+ CURLM *curlmulti;
+};
+
+struct PipeLinkExtractor {
+ PipeComponent pipe_data;
+ LinkExtractor *link_extractor;
+};
+
+struct PipeCallbackWrapper {
+ PipeComponent pipe_data;
+ void *write_data;
+ void *finish_data;
+ ssize_t (*write_callback)(const char *, size_t, void *);
+ void (*finish_callback)(RequestState, void *);
+};
+
+struct PipeMainMenuDownloader {
+ PipeComponent pipe_data;
+ const WebviContext *context; /* borrowed reference */
+};
+
+struct PipeExternalDownloader {
+ PipeComponent pipe_data;
+ gchar *url;
+ int fd;
+};
+
+struct PipeLocalFile {
+ PipeComponent pipe_data;
+ gchar *filename;
+ int fd;
+};
+
+struct PipeLibquvi {
+ PipeComponent pipe_data;
+ gchar *url;
+ GPid pid;
+ gint quvi_output;
+ xmlParserCtxtPtr parser;
+};
+
+static void pipe_component_delete(PipeComponent *self);
+
+static gboolean pipe_link_extractor_append(PipeComponent *instance, char *buf, size_t len);
+static void pipe_link_extractor_finished(PipeComponent *instance, RequestState state);
+static void pipe_link_extractor_delete(PipeComponent *instance);
+
+static gboolean pipe_callback_wrapper_process(PipeComponent *instance,
+ char *buf, size_t len);
+static void pipe_callback_wrapper_finished(PipeComponent *instance,
+ RequestState state);
+static void pipe_callback_wrapper_delete(PipeComponent *instance);
+
+static void pipe_downloader_finished(PipeComponent *instance, RequestState state);
+static void pipe_downloader_delete(PipeComponent *instance);
+static size_t curl_write_wrapper(char *ptr, size_t size, size_t nmemb, void *userdata);
+
+static void pipe_mainmenu_downloader_delete(PipeComponent *instance);
+
+static void pipe_local_file_fdset(PipeComponent *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *maxfd);
+static gboolean pipe_local_file_handle_socket(PipeComponent *instance,
+ int fd, int bitmask);
+static void pipe_local_file_delete(PipeComponent *instance);
+
+static void pipe_external_downloader_fdset(PipeComponent *instance,
+ fd_set *readfd, fd_set *writefd,
+ fd_set *excfd, int *maxfd);
+static gboolean pipe_external_downloader_handle_socket(
+ PipeComponent *instance, int fd, int bitmask);
+static void pipe_external_downloader_delete(PipeComponent *instance);
+
+static void pipe_libquvi_fdset(PipeComponent *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *maxfd);
+static gboolean pipe_libquvi_handle_socket(PipeComponent *instance,
+ int fd, int bitmask);
+static gboolean pipe_libquvi_parse(PipeComponent *instance, char *buf, size_t len);
+static void pipe_libquvi_finished(PipeComponent *instance, RequestState state);
+static xmlChar *quvi_xml_get_stream_url(xmlDoc *doc);
+static xmlChar *quvi_xml_get_stream_title(xmlDoc *doc);
+static void pipe_libquvi_delete(PipeComponent *instance);
+
+static CURL *start_curl(const char *url, CURLM *curlmulti,
+ PipeComponent *instance);
+static void append_to_fdset(int fd, fd_set *fdset, int *maxfd);
+static gboolean read_from_fd_to_pipe(PipeComponent *instance,
+ int *instance_fd_ptr, int fd, int bitmask);
+
+
+void pipe_component_initialize(PipeComponent *self,
+ gboolean (*process_cb)(PipeComponent *, char *, size_t),
+ void (*done_cb)(PipeComponent *, RequestState),
+ void (*delete_cb)(PipeComponent *))
+{
+ g_assert(self);
+ g_assert(delete_cb);
+
+ memset(self, 0, sizeof(PipeComponent));
+ self->process = process_cb;
+ self->finished = done_cb;
+ self->delete = delete_cb;
+ self->state = WEBVISTATE_NOT_FINISHED;
+}
+
+void pipe_component_initialize_fdset(PipeComponent *self,
+ gboolean (*process_cb)(PipeComponent *, char *, size_t),
+ void (*done_cb)(PipeComponent *, RequestState),
+ void (*delete_cb)(PipeComponent *),
+ void (*fdset_cb)(PipeComponent *, fd_set *, fd_set *, fd_set *, int *),
+ gboolean (*handle_socket_cb)(PipeComponent *, int, int))
+{
+ pipe_component_initialize(self, process_cb, done_cb, delete_cb);
+ self->fdset = fdset_cb;
+ self->handle_socket = handle_socket_cb;
+}
+
+void pipe_component_set_next(PipeComponent *self, PipeComponent *next) {
+ g_assert(!self->next);
+ self->next = next;
+}
+
+void pipe_component_append(PipeComponent *self, char *buf, size_t length) {
+ if (self->state == WEBVISTATE_NOT_FINISHED) {
+ gboolean propagate = TRUE;
+ if (self->process)
+ propagate = self->process(self, buf, length);
+ if (propagate && self->next)
+ pipe_component_append(self->next, buf, length);
+ }
+}
+
+void pipe_component_finished(PipeComponent *self, RequestState state) {
+ if (self->state == WEBVISTATE_NOT_FINISHED) {
+ self->state = state;
+ if (self->finished)
+ self->finished(self, state);
+ if (self->next)
+ pipe_component_finished(self->next, state);
+ }
+}
+
+RequestState pipe_component_get_state(const PipeComponent *self) {
+ return self->state;
+}
+
+void pipe_fdset(PipeComponent *head, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *max_fd)
+{
+ PipeComponent *component = head;
+ PipeComponent *next;
+ while (component) {
+ next = component->next;
+ if (component->fdset) {
+ component->fdset(component, readfd, writefd, excfd, max_fd);
+ }
+ component = next;
+ }
+}
+
+gboolean pipe_handle_socket(PipeComponent *head, int sockfd, int ev_bitmask) {
+ PipeComponent *component = head;
+ PipeComponent *next;
+ while (component) {
+ next = component->next;
+ if (component->handle_socket) {
+ if (component->handle_socket(component, sockfd, ev_bitmask)) {
+ return TRUE;
+ }
+ }
+ component = next;
+ }
+ return FALSE;
+}
+
+void pipe_component_delete(PipeComponent *self) {
+ if (self && self->delete)
+ self->delete(self);
+}
+
+void pipe_delete_full(PipeComponent *head) {
+ PipeComponent *component = head;
+ PipeComponent *next;
+ while (component) {
+ next = component->next;
+ pipe_component_delete(component);
+ component = next;
+ }
+}
+
+
+/***** PipeLinkExtractor *****/
+
+
+PipeLinkExtractor *pipe_link_extractor_create(
+ const LinkTemplates *link_templates, const gchar *baseurl)
+{
+ INITIALIZE_PIPE(PipeLinkExtractor, pipe_link_extractor_append,
+ pipe_link_extractor_finished, pipe_link_extractor_delete);
+ self->link_extractor = link_extractor_create(link_templates, baseurl);
+ return self;
+}
+
+void pipe_link_extractor_delete(PipeComponent *instance) {
+ PipeLinkExtractor *self = (PipeLinkExtractor *)instance;
+ link_extractor_delete(self->link_extractor);
+ free(self);
+}
+
+gboolean pipe_link_extractor_append(PipeComponent *instance, char *buf, size_t len) {
+ PipeLinkExtractor *self = (PipeLinkExtractor *)instance;
+ link_extractor_append(self->link_extractor, buf, len);
+ return FALSE;
+}
+
+void pipe_link_extractor_finished(PipeComponent *instance,
+ RequestState state)
+{
+ PipeLinkExtractor *self = (PipeLinkExtractor *)instance;
+ if (state == WEBVISTATE_FINISHED_OK) {
+ GPtrArray *links = link_extractor_get_links(self->link_extractor);
+ MenuBuilder *menu_builder = menu_builder_create();
+ menu_builder_append_link_list(menu_builder, links);
+ char *menu = menu_builder_to_string(menu_builder);
+ if (self->pipe_data.next) {
+ pipe_component_append(self->pipe_data.next, menu, strlen(menu));
+ pipe_component_finished(self->pipe_data.next, state);
+ }
+ menu_builder_delete(menu_builder);
+ free(menu);
+ g_ptr_array_free(links, TRUE);
+ }
+}
+
+
+/***** PipeDownloader *****/
+
+
+PipeDownloader *pipe_downloader_create(const char *url, CURLM *curlmulti) {
+ INITIALIZE_PIPE(PipeDownloader, NULL, pipe_downloader_finished,
+ pipe_downloader_delete);
+ self->curl = start_curl(url, curlmulti, (PipeComponent *)self);
+ if (!self->curl) {
+ pipe_downloader_delete((PipeComponent *)self);
+ return NULL;
+ }
+ self->curlmulti = curlmulti;
+ return self;
+}
+
+void pipe_downloader_start(PipeDownloader *self) {
+ CURLMcode mcode = curl_multi_add_handle(self->curlmulti, self->curl);
+ if (mcode != CURLM_OK) {
+ pipe_component_finished(&self->pipe_data, WEBVISTATE_INTERNAL_ERROR);
+ }
+}
+
+size_t curl_write_wrapper(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ PipeComponent *pipedata = (PipeComponent *)userdata;
+ if (pipe_component_get_state(pipedata) == WEBVISTATE_NOT_FINISHED) {
+ pipe_component_append(pipedata, ptr, size*nmemb);
+ return size*nmemb;
+ } else {
+ return 0;
+ }
+}
+
+void pipe_downloader_finished(PipeComponent *instance, RequestState state) {
+ PipeDownloader *self = (PipeDownloader *)instance;
+ curl_multi_remove_handle(self->curlmulti, self->curl);
+}
+
+void pipe_downloader_delete(PipeComponent *instance) {
+ PipeDownloader *self = (PipeDownloader *)instance;
+ if (self->pipe_data.state == WEBVISTATE_NOT_FINISHED) {
+ curl_multi_remove_handle(self->curlmulti, self->curl);
+ }
+ curl_easy_cleanup(self->curl);
+ free(instance);
+}
+
+
+/***** PipeMainMenuDownloader *****/
+
+
+PipeMainMenuDownloader *pipe_mainmenu_downloader_create(WebviContext *context) {
+ INITIALIZE_PIPE(PipeMainMenuDownloader, NULL, NULL,
+ pipe_mainmenu_downloader_delete);
+ self->context = context;
+ return self;
+}
+
+void pipe_mainmenu_downloader_start(PipeMainMenuDownloader *self) {
+ char *mainmenu = build_mainmenu(webvi_context_get_template_path(self->context));
+ if (!mainmenu) {
+ pipe_component_finished(&self->pipe_data, WEBVISTATE_INTERNAL_ERROR);
+ return;
+ }
+
+ pipe_component_append(&self->pipe_data, mainmenu, strlen(mainmenu));
+ pipe_component_finished(&self->pipe_data, WEBVISTATE_FINISHED_OK);
+
+ g_free(mainmenu);
+}
+
+void pipe_mainmenu_downloader_delete(PipeComponent *instance) {
+ PipeMainMenuDownloader *self = (PipeMainMenuDownloader *)instance;
+ free(self);
+}
+
+
+/***** PipeCallbackWrapper *****/
+
+
+PipeCallbackWrapper *pipe_callback_wrapper_create(
+ ssize_t (*write_callback)(const char *, size_t, void *),
+ void *writedata,
+ void (*finish_callback)(RequestState, void *),
+ void *finishdata)
+{
+ INITIALIZE_PIPE(PipeCallbackWrapper,
+ pipe_callback_wrapper_process,
+ pipe_callback_wrapper_finished,
+ pipe_callback_wrapper_delete);
+ self->write_data = writedata;
+ self->finish_data = finishdata;
+ self->write_callback = write_callback;
+ self->finish_callback = finish_callback;
+ return self;
+}
+
+gboolean pipe_callback_wrapper_process(PipeComponent *instance,
+ char *buf, size_t len)
+{
+ PipeCallbackWrapper *self = (PipeCallbackWrapper *)instance;
+ if (self->write_callback)
+ self->write_callback(buf, len, self->write_data);
+ return TRUE;
+}
+
+void pipe_callback_wrapper_finished(PipeComponent *instance,
+ RequestState state)
+{
+ PipeCallbackWrapper *self = (PipeCallbackWrapper *)instance;
+ if (self->finish_callback)
+ self->finish_callback(state, self->finish_data);
+}
+
+void pipe_callback_wrapper_delete(PipeComponent *instance) {
+ PipeCallbackWrapper *self = (PipeCallbackWrapper *)instance;
+ free(self);
+}
+
+PipeLocalFile *pipe_local_file_create(const gchar *filename) {
+ INITIALIZE_PIPE_WITH_FDSET(PipeLocalFile, NULL, NULL,
+ pipe_local_file_delete,
+ pipe_local_file_fdset,
+ pipe_local_file_handle_socket);
+ self->filename = g_strdup(filename);
+ self->fd = -1;
+ return self;
+}
+
+void pipe_local_file_start(PipeLocalFile *self) {
+ if (!self->filename) {
+ pipe_component_finished((PipeComponent *)self, WEBVISTATE_NOT_FOUND);
+ }
+
+ self->fd = open(self->filename, O_RDONLY);
+ if (self->fd == -1) {
+ pipe_component_finished((PipeComponent *)self, WEBVISTATE_NOT_FOUND);
+ }
+}
+
+void pipe_local_file_fdset(PipeComponent *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *maxfd)
+{
+ PipeLocalFile *self = (PipeLocalFile *)instance;
+ append_to_fdset(self->fd, readfd, maxfd);
+}
+
+gboolean pipe_local_file_handle_socket(PipeComponent *instance, int fd, int bitmask) {
+ PipeLocalFile *self = (PipeLocalFile *)instance;
+ return read_from_fd_to_pipe(instance, &self->fd, fd, bitmask);
+}
+
+void pipe_local_file_delete(PipeComponent *instance) {
+ PipeLocalFile *self = (PipeLocalFile *)instance;
+ g_free(self->filename);
+ if (self->fd != -1)
+ close(self->fd);
+ free(self);
+}
+
+
+/***** PipeExternalDownloader *****/
+
+
+PipeExternalDownloader *pipe_external_downloader_create(const gchar *url,
+ const gchar *command)
+{
+ INITIALIZE_PIPE_WITH_FDSET(PipeExternalDownloader, NULL, NULL,
+ pipe_external_downloader_delete,
+ pipe_external_downloader_fdset,
+ pipe_external_downloader_handle_socket);
+ self->url = g_strdup(url);
+ self->fd = -1;
+
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+ "external downloader:\nurl: %s\n%s", url, command);
+
+ return self;
+}
+
+void pipe_external_downloader_start(PipeExternalDownloader *self) {
+ // FIXME
+ pipe_component_finished((PipeComponent *)self, WEBVISTATE_INTERNAL_ERROR);
+}
+
+void pipe_external_downloader_fdset(PipeComponent *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *maxfd)
+{
+ PipeExternalDownloader *self = (PipeExternalDownloader *)instance;
+ append_to_fdset(self->fd, readfd, maxfd);
+}
+
+gboolean pipe_external_downloader_handle_socket(PipeComponent *instance,
+ int fd, int bitmask)
+{
+ PipeExternalDownloader *self = (PipeExternalDownloader *)instance;
+ return read_from_fd_to_pipe(instance, &self->fd, fd, bitmask);
+}
+
+void pipe_external_downloader_delete(PipeComponent *instance) {
+ PipeExternalDownloader *self = (PipeExternalDownloader *)instance;
+ if (self->fd != -1)
+ close(self->fd);
+ g_free(self->url);
+ g_free(self);
+}
+
+
+/***** PipeLibquvi *****/
+
+
+PipeLibquvi *pipe_libquvi_create(const gchar *url) {
+ INITIALIZE_PIPE_WITH_FDSET(PipeLibquvi,
+ pipe_libquvi_parse,
+ pipe_libquvi_finished,
+ pipe_libquvi_delete,
+ pipe_libquvi_fdset,
+ pipe_libquvi_handle_socket);
+ self->url = g_strdup(url);
+ self->pid = -1;
+ self->quvi_output = -1;
+ return self;
+}
+
+void pipe_libquvi_start(PipeLibquvi *self) {
+ GError *error = NULL;
+ gchar *argv[] = {"quvi", "--xml", self->url};
+
+ g_spawn_async_with_pipes(NULL, argv, NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL, &self->pid, NULL, &self->quvi_output,
+ NULL, &error);
+ if (error) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Calling quvi failed: %s", error->message);
+ pipe_component_finished((PipeComponent *)self, WEBVISTATE_SUBPROCESS_FAILED);
+ }
+}
+
+void pipe_libquvi_fdset(PipeComponent *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *maxfd)
+{
+ PipeLibquvi *self = (PipeLibquvi *)instance;
+ append_to_fdset(self->quvi_output, readfd, maxfd);
+}
+
+gboolean pipe_libquvi_handle_socket(PipeComponent *instance,
+ int fd, int bitmask)
+{
+ PipeLibquvi *self = (PipeLibquvi *)instance;
+ return read_from_fd_to_pipe(instance, &self->quvi_output, fd, bitmask);
+}
+
+gboolean pipe_libquvi_parse(PipeComponent *instance, char *buf, size_t len) {
+ PipeLibquvi *self = (PipeLibquvi *)instance;
+ if (!self->parser) {
+ self->parser = xmlCreatePushParserCtxt(NULL, NULL, buf, len, "quvioutput.xml");
+ g_assert(self->parser);
+ } else {
+ xmlParseChunk(self->parser, buf, len, 0);
+ }
+
+ return FALSE;
+}
+
+void pipe_libquvi_finished(PipeComponent *instance, RequestState state) {
+ if (state != WEBVISTATE_FINISHED_OK) {
+ pipe_component_finished(instance, state);
+ return;
+ }
+
+ PipeLibquvi *self = (PipeLibquvi *)instance;
+ if (!self->parser) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "No output from quvi!");
+ pipe_component_finished(instance, WEBVISTATE_SUBPROCESS_FAILED);
+ return;
+ }
+
+ xmlParseChunk(self->parser, NULL, 0, 1);
+ xmlDoc *doc = self->parser->myDoc;
+
+ xmlChar *dump;
+ int dumpLen;
+ xmlDocDumpMemory(doc, &dump, &dumpLen);
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "quvi output:\n%s", dump);
+ xmlFree(dump);
+ dump = NULL;
+
+ xmlChar *encoded_url = quvi_xml_get_stream_url(doc);
+ if (!encoded_url) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "No url in quvi output!");
+ pipe_component_finished(instance, WEBVISTATE_SUBPROCESS_FAILED);
+ return;
+ }
+
+ /* URLs in quvi XML output are encoded by curl_easy_escape */
+ char *url = curl_unescape((char *)encoded_url, strlen((char *)encoded_url));
+ xmlChar *title = quvi_xml_get_stream_title(doc);
+
+ MenuBuilder *menu_builder = menu_builder_create();
+ menu_builder_set_title(menu_builder, (char *)title);
+ menu_builder_append_link_plain(menu_builder, url, (char *)title, NULL);
+ char *menu = menu_builder_to_string(menu_builder);
+ if (self->pipe_data.next) {
+ pipe_component_append(self->pipe_data.next, menu, strlen(menu));
+ pipe_component_finished(self->pipe_data.next, state);
+ }
+
+ free(menu);
+ menu_builder_delete(menu_builder);
+ xmlFree(title);
+ curl_free(url);
+ xmlFree(encoded_url);
+}
+
+xmlChar *quvi_xml_get_stream_url(xmlDoc *doc) {
+ xmlNode *root = xmlDocGetRootElement(doc);
+ xmlNode *node = root->children;
+ while (node) {
+ if (xmlStrEqual(node->name, BAD_CAST "link")) {
+ xmlNode *link_child = node->children;
+ while (link_child) {
+ if (xmlStrEqual(link_child->name, BAD_CAST "url")) {
+ return xmlNodeGetContent(link_child);
+ }
+ link_child = link_child->next;
+ }
+ }
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+xmlChar *quvi_xml_get_stream_title(xmlDoc *doc) {
+ xmlNode *root = xmlDocGetRootElement(doc);
+ xmlNode *node = root->children;
+ while (node) {
+ if (xmlStrEqual(node->name, BAD_CAST "page_title")) {
+ return xmlNodeGetContent(node);
+ }
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+void pipe_libquvi_delete(PipeComponent *instance) {
+ PipeLibquvi *self = (PipeLibquvi *)instance;
+ if (self->quvi_output != -1) {
+ close(self->quvi_output);
+ }
+ if (self->pid != -1) {
+ g_spawn_close_pid(self->pid);
+ }
+ if (self->parser) {
+ xmlFreeParserCtxt(self->parser);
+ }
+ g_free(self->url);
+ free(self);
+}
+
+
+/***** Utility functions *****/
+
+
+CURL *start_curl(const char *url, CURLM *curlmulti, PipeComponent *instance) {
+ CURL *curl = curl_easy_init();
+ if (!curl) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "curl initialization failed");
+ return NULL;
+ }
+
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Downloading %s", url);
+
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, WEBVID_USER_AGENT);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_wrapper);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, instance);
+ if (url)
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_PRIVATE, instance);
+
+ // FIXME: headers, cookies
+
+ return curl;
+}
+
+void append_to_fdset(int fd, fd_set *fdset, int *maxfd) {
+ if (fd != -1) {
+ FD_SET(fd, fdset);
+ if (fd > *maxfd)
+ *maxfd = fd;
+ }
+}
+
+gboolean read_from_fd_to_pipe(PipeComponent *instance, int *instance_fd_ptr,
+ int fd, int bitmask)
+{
+ const int buflen = 4096;
+ char buf[buflen];
+ ssize_t numbytes;
+
+ gboolean owned_socket = (fd == -1) || (fd == *instance_fd_ptr);
+ gboolean read_operation = ((bitmask & WEBVI_SELECT_READ) != 0) ||
+ (bitmask == WEBVI_SELECT_CHECK);
+
+ if (owned_socket && read_operation) {
+ numbytes = read(fd, buf, buflen);
+ if (numbytes < 0) {
+ /* error */
+ pipe_component_finished(instance, WEBVISTATE_IO_ERROR);
+ } else if (numbytes == 0) {
+ /* end of file */
+ pipe_component_finished(instance, WEBVISTATE_FINISHED_OK);
+ close(fd);
+ *instance_fd_ptr = -1;
+ } else {
+ pipe_component_append(instance, buf, numbytes);
+ }
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
diff --git a/src/libwebvi/pipecomponent.h b/src/libwebvi/pipecomponent.h
new file mode 100644
index 0000000..00a05ef
--- /dev/null
+++ b/src/libwebvi/pipecomponent.h
@@ -0,0 +1,95 @@
+/*
+ * pipecomponent.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PIPECOMPONENT_H
+#define __PIPECOMPONENT_H
+
+#include <stdlib.h>
+#include <sys/select.h>
+#include <glib.h>
+#include <curl/curl.h>
+#include "libwebvi.h"
+
+typedef struct PipeComponent {
+ struct PipeComponent *next;
+ RequestState state;
+ gboolean (*process)(struct PipeComponent *self, char *, size_t);
+ void (*finished)(struct PipeComponent *self, RequestState state);
+ void (*delete)(struct PipeComponent *self);
+ void (*fdset)(struct PipeComponent *self, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *max_fd);
+ gboolean (*handle_socket)(struct PipeComponent *self, int fd, int ev_bitmask);
+} PipeComponent;
+
+struct WebviContext;
+struct LinkTemplates;
+
+typedef struct PipeDownloader PipeDownloader;
+typedef struct PipeLinkExtractor PipeLinkExtractor;
+typedef struct PipeCallbackWrapper PipeCallbackWrapper;
+typedef struct PipeMainMenuDownloader PipeMainMenuDownloader;
+typedef struct PipeLocalFile PipeLocalFile;
+typedef struct PipeExternalDownloader PipeExternalDownloader;
+typedef struct PipeLibquvi PipeLibquvi;
+
+void pipe_component_initialize(PipeComponent *self,
+ gboolean (*process_cb)(PipeComponent *, char *, size_t),
+ void (*done_cb)(PipeComponent *, RequestState state),
+ void (*delete_cb)(PipeComponent *));
+void pipe_component_initialize_fdset(PipeComponent *self,
+ gboolean (*process_cb)(PipeComponent *, char *, size_t),
+ void (*done_cb)(PipeComponent *, RequestState state),
+ void (*delete_cb)(PipeComponent *),
+ void (*fdset_cb)(PipeComponent *, fd_set *, fd_set *, fd_set *, int *),
+ gboolean (*handle_socket_cb)(PipeComponent *, int, int));
+void pipe_component_append(PipeComponent *self, char *buf, size_t length);
+void pipe_component_finished(PipeComponent *self, RequestState state);
+void pipe_component_set_next(PipeComponent *self, PipeComponent *next);
+RequestState pipe_component_get_state(const PipeComponent *self);
+void pipe_fdset(PipeComponent *head, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *max_fd);
+gboolean pipe_handle_socket(PipeComponent *head, int sockfd, int ev_bitmask);
+void pipe_delete_full(PipeComponent *head);
+
+PipeDownloader *pipe_downloader_create(const char *url, CURLM *curlmulti);
+void pipe_downloader_start(PipeDownloader *self);
+
+PipeMainMenuDownloader *pipe_mainmenu_downloader_create(struct WebviContext *context);
+void pipe_mainmenu_downloader_start(PipeMainMenuDownloader *self);
+
+PipeLinkExtractor *pipe_link_extractor_create(
+ const struct LinkTemplates *link_templates, const gchar *baseurl);
+
+PipeLocalFile *pipe_local_file_create(const gchar *filename);
+void pipe_local_file_start(PipeLocalFile *self);
+
+PipeCallbackWrapper *pipe_callback_wrapper_create(
+ ssize_t (*write_callback)(const char *, size_t, void *),
+ void *writedata,
+ void (*finish_callback)(RequestState, void *),
+ void *finishdata);
+
+PipeExternalDownloader *pipe_external_downloader_create(const gchar *url,
+ const gchar *command);
+void pipe_external_downloader_start(PipeExternalDownloader *self);
+
+PipeLibquvi *pipe_libquvi_create(const gchar *url);
+void pipe_libquvi_start(PipeLibquvi *self);
+
+#endif // __PIPECOMPONENT_H
diff --git a/src/libwebvi/request.c b/src/libwebvi/request.c
new file mode 100644
index 0000000..af078c4
--- /dev/null
+++ b/src/libwebvi/request.c
@@ -0,0 +1,237 @@
+#include <glib.h>
+#include <string.h>
+#include "pipecomponent.h"
+#include "webvicontext.h"
+#include "request.h"
+#include "link.h"
+
+struct WebviRequest {
+ PipeComponent *pipe_head;
+ gchar *url;
+ webvi_callback write_cb;
+ webvi_callback read_cb;
+ void *writedata;
+ void *readdata;
+ struct WebviContext *ctx; /* borrowed reference */
+};
+
+struct RequestStateMessage {
+ RequestState state;
+ const char *message;
+};
+
+static PipeComponent *pipe_factory(const WebviRequest *self);
+static void notify_pipe_finished(RequestState state, void *data);
+static const char *pipe_state_to_message(RequestState state);
+static gchar *wvt_to_local_file(const WebviContext *ctx, const char *wvt);
+static PipeComponent *build_and_start_mainmenu_pipe(const WebviRequest *self);
+static PipeComponent *build_and_start_local_pipe(const WebviRequest *self);
+static PipeComponent *build_and_start_external_pipe(const WebviRequest *self,
+ const char *command);
+static PipeComponent *build_and_start_libquvi_pipe(const WebviRequest *self);
+static PipeComponent *build_and_start_menu_pipe(const WebviRequest *self);
+
+WebviRequest *request_create(const char *url, struct WebviContext *ctx) {
+ WebviRequest *req = malloc(sizeof(WebviRequest));
+ memset(req, 0, sizeof(WebviRequest));
+ req->url = g_strdup(url);
+ req->ctx = ctx;
+ return req;
+}
+
+void request_delete(WebviRequest *self) {
+ if (self) {
+ request_stop(self);
+ pipe_delete_full(self->pipe_head);
+ g_free(self->url);
+ }
+}
+
+gboolean request_start(WebviRequest *self) {
+ if (!self->pipe_head) {
+ self->pipe_head = pipe_factory(self);
+ if (!self->pipe_head) {
+ notify_pipe_finished(WEBVISTATE_NOT_FOUND, self);
+ }
+ }
+ return TRUE;
+}
+
+void request_stop(WebviRequest *self) {
+ // FIXME
+}
+
+PipeComponent *pipe_factory(const WebviRequest *self) {
+ PipeComponent *head;
+ if (strcmp(self->url, "wvt://mainmenu") == 0) {
+ head = build_and_start_mainmenu_pipe(self);
+
+ } else if (strncmp(self->url, "wvt://", 6) == 0) {
+ head = build_and_start_local_pipe(self);
+
+ } else {
+ const LinkAction *action = link_templates_get_action(
+ get_link_templates(self->ctx), self->url);
+ LinkActionType action_type = action ? link_action_get_type(action) : LINK_ACTION_PARSE;
+ if (action_type == LINK_ACTION_STREAM_LIBQUVI) {
+ head = build_and_start_libquvi_pipe(self);
+ } else if (action_type == LINK_ACTION_EXTERNAL_COMMAND) {
+ const char *command = link_action_get_command(action);
+ head = build_and_start_external_pipe(self, command);
+ } else {
+ head = build_and_start_menu_pipe(self);
+ }
+ }
+
+ return head;
+}
+
+PipeComponent *build_and_start_mainmenu_pipe(const WebviRequest *self) {
+ PipeMainMenuDownloader *p1 = pipe_mainmenu_downloader_create(self->ctx);
+ PipeComponent *p2 = (PipeComponent *)pipe_callback_wrapper_create(
+ self->read_cb, self->readdata, notify_pipe_finished, (void *)self);
+ pipe_component_set_next((PipeComponent *)p1, p2);
+ pipe_mainmenu_downloader_start(p1);
+
+ return (PipeComponent *)p1;
+}
+
+PipeComponent *build_and_start_local_pipe(const WebviRequest *self) {
+ gchar *filename = wvt_to_local_file(self->ctx, self->url);
+ PipeLocalFile *p1 = pipe_local_file_create(filename);
+ g_free(filename);
+ PipeComponent *p2 = (PipeComponent *)pipe_callback_wrapper_create(
+ self->read_cb, self->readdata, notify_pipe_finished, (void *)self);
+ pipe_component_set_next((PipeComponent *)p1, p2);
+ pipe_local_file_start(p1);
+
+ return (PipeComponent *)p1;
+}
+
+PipeComponent *build_and_start_external_pipe(const WebviRequest *self, const char *command) {
+ PipeExternalDownloader *p1 = pipe_external_downloader_create(self->url, command);
+ PipeComponent *p2 = (PipeComponent *)pipe_callback_wrapper_create(
+ self->read_cb, self->readdata, notify_pipe_finished, (void *)self);
+ pipe_component_set_next((PipeComponent *)p1, p2);
+ pipe_external_downloader_start(p1);
+
+ return (PipeComponent *)p1;
+}
+
+PipeComponent *build_and_start_libquvi_pipe(const WebviRequest *self) {
+ PipeLibquvi *p1 = pipe_libquvi_create(self->url);
+ PipeComponent *p2 = (PipeComponent *)pipe_callback_wrapper_create(
+ self->read_cb, self->readdata, notify_pipe_finished, (void *)self);
+ pipe_component_set_next((PipeComponent *)p1, p2);
+ pipe_libquvi_start(p1);
+
+ return (PipeComponent *)p1;
+}
+
+PipeComponent *build_and_start_menu_pipe(const WebviRequest *self) {
+ CURLM *curlmulti = webvi_context_get_curl_multi_handle(self->ctx);
+ PipeDownloader *p1 = pipe_downloader_create(self->url, curlmulti);
+ PipeComponent *p2 = (PipeComponent *)pipe_link_extractor_create(
+ get_link_templates(self->ctx), self->url);
+ PipeComponent *p3 = (PipeComponent *)pipe_callback_wrapper_create(
+ self->read_cb, self->readdata, notify_pipe_finished, (void *)self);
+ pipe_component_set_next((PipeComponent *)p1, p2);
+ pipe_component_set_next(p2, p3);
+ pipe_downloader_start(p1);
+
+ return (PipeComponent *)p1;
+}
+
+gchar *wvt_to_local_file(const WebviContext *ctx, const char *wvt) {
+ if (strncmp(wvt, "wvt://", 6) != 0)
+ return NULL;
+
+ const gchar *template_path = webvi_context_get_template_path(ctx);
+ if (!template_path)
+ return NULL; // FIXME
+
+ // FIXME: .. in paths
+
+ gchar *filename = g_strdup(wvt+6);
+ if (filename[0] == '/') {
+ // absolute path
+ // The path must be located under the template directory
+ if (strncmp(filename, template_path, strlen(template_path)) != 0) {
+ g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+ "Invalid path in wvt:// url");
+ g_free(filename);
+ return NULL;
+ }
+ } else {
+ // relative path, concatenate to template_path
+ gchar *absolute_path = g_strconcat(template_path, "/", filename, NULL);
+ g_free(filename);
+ filename = absolute_path;
+ }
+
+ return filename;
+}
+
+void request_fdset(WebviRequest *self, fd_set *readfds,
+ fd_set *writefds, fd_set *excfds, int *max_fd)
+{
+ if (self->pipe_head) {
+ pipe_fdset(self->pipe_head, readfds, writefds, excfds, max_fd);
+ }
+}
+
+gboolean request_handle_socket(WebviRequest *self, int sockfd, int ev_bitmask)
+{
+ if (self->pipe_head) {
+ return pipe_handle_socket(self->pipe_head, sockfd, ev_bitmask);
+ } else {
+ return FALSE;
+ }
+}
+
+void request_set_write_callback(WebviRequest *self, webvi_callback func) {
+ self->write_cb = func;
+}
+
+void request_set_read_callback(WebviRequest *self, webvi_callback func) {
+ self->read_cb = func;
+}
+
+void request_set_write_data(WebviRequest *self, void *data) {
+ self->writedata = data;
+}
+
+void request_set_read_data(WebviRequest *self, void *data) {
+ self->readdata = data;
+}
+
+const char *request_get_url(const WebviRequest *self) {
+ return self->url;
+}
+
+void notify_pipe_finished(RequestState state, void *data) {
+ WebviRequest *req = (WebviRequest *)data;
+ const char *msg = pipe_state_to_message(state);
+ webvi_context_add_finished_message(req->ctx, req, state, msg);
+}
+
+const char *pipe_state_to_message(RequestState state) {
+ static struct RequestStateMessage messages[] =
+ {{WEBVISTATE_NOT_FINISHED, "Not finished"},
+ {WEBVISTATE_FINISHED_OK, "Success"},
+ {WEBVISTATE_MEMORY_ALLOCATION_ERROR, "Out of memory"},
+ {WEBVISTATE_NOT_FOUND, "Not found"},
+ {WEBVISTATE_NETWORK_READ_ERROR, "Failed to receive data from the network"},
+ {WEBVISTATE_IO_ERROR, "IO error"},
+ {WEBVISTATE_TIMEDOUT, "Timedout"},
+ {WEBVISTATE_SUBPROCESS_FAILED, "Failed to execute a subprocess"},
+ {WEBVISTATE_INTERNAL_ERROR, "Internal error"}};
+
+ for (int i=0; i<(sizeof(messages)/sizeof(messages[0])); i++) {
+ if (state == messages[i].state) {
+ return messages[i].message;
+ }
+ }
+
+ return "Internal error";
+}
diff --git a/src/libwebvi/request.h b/src/libwebvi/request.h
new file mode 100644
index 0000000..c52c91e
--- /dev/null
+++ b/src/libwebvi/request.h
@@ -0,0 +1,42 @@
+/*
+ * request.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __REQUEST_H
+#define __REQUEST_H
+
+#include "pipecomponent.h"
+
+struct WebviContext;
+
+typedef struct WebviRequest WebviRequest;
+
+WebviRequest *request_create(const char *url, struct WebviContext *ctx);
+void request_set_write_callback(WebviRequest *instance, webvi_callback func);
+void request_set_read_callback(WebviRequest *instance, webvi_callback func);
+void request_set_write_data(WebviRequest *instance, void *data);
+void request_set_read_data(WebviRequest *instance, void *data);
+const char *request_get_url(const WebviRequest *instance);
+gboolean request_start(WebviRequest *instance);
+void request_stop(WebviRequest *instance);
+void request_fdset(WebviRequest *instance, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *max_fd);
+gboolean request_handle_socket(WebviRequest *instance, int sockfd, int ev_bitmask);
+void request_delete(WebviRequest *instance);
+
+#endif // __REQUEST_H
diff --git a/src/libwebvi/urlutils.c b/src/libwebvi/urlutils.c
new file mode 100644
index 0000000..de64e06
--- /dev/null
+++ b/src/libwebvi/urlutils.c
@@ -0,0 +1,132 @@
+#include <string.h>
+#include "urlutils.h"
+
+static const gchar *skip_scheme(const char *url);
+static gboolean is_scheme_character(gchar c);
+
+gchar *relative_url_to_absolute(const gchar *baseurl, const gchar *href) {
+ gchar *absolute;
+ gchar *prefix;
+ const gchar *postfix = href;
+ if ((href[0] == '/') && (href[1] == '/')) {
+ gchar *scheme = url_scheme(baseurl);
+ prefix = g_strconcat(scheme, ":", NULL);
+ g_free(scheme);
+ } else if (href[0] == '/') {
+ prefix = url_root(baseurl);
+ if (g_str_has_suffix(prefix, "/")) {
+ postfix = href+1;
+ }
+ } else if (href[0] == '?') {
+ prefix = url_path_including_file(baseurl);
+ } else if (href[0] =='#') {
+ prefix = url_path_and_query(baseurl);
+ } else if (strstr(href, "://") == NULL) {
+ prefix = url_path_dirname(baseurl);
+ } else {
+ // href is absolute
+ prefix = NULL;
+ }
+
+ if (prefix) {
+ absolute = g_strconcat(prefix, postfix, NULL);
+ g_free(prefix);
+ } else {
+ absolute = g_strdup(href);
+ }
+
+ return absolute;
+}
+
+gchar *url_scheme(const gchar *url) {
+ if (!url)
+ return NULL;
+
+ const gchar *scheme_end = skip_scheme(url);
+ if (scheme_end == url) {
+ // no scheme
+ return g_strdup("");
+ } else {
+ // scheme found
+ // Do not include :// in the return value
+ g_assert(scheme_end >= url+3);
+ return g_strndup(url, scheme_end-3 - url);
+ }
+}
+
+gchar *url_root(const gchar *url) {
+ if (!url)
+ return NULL;
+
+ const gchar *authority = skip_scheme(url);
+ size_t authority_len = strcspn(authority, "/?#");
+ const gchar *authority_end = authority + authority_len;
+ gchar *root_without_slash = g_strndup(url, authority_end - url);
+ gchar *root = g_strconcat(root_without_slash, "/", NULL);
+ g_free(root_without_slash);
+ return root;
+}
+
+gchar *url_path_including_file(const gchar *url) {
+ if (!url)
+ return NULL;
+
+ const gchar *scheme_end = skip_scheme(url);
+ size_t path_len = strcspn(scheme_end, "?#");
+ const gchar *end = scheme_end + path_len;
+ gchar *path = g_strndup(url, end - url);
+ if (memchr(scheme_end, '/', path_len) == NULL) {
+ gchar *path2 = g_strconcat(path, "/", NULL);
+ g_free(path);
+ path = path2;
+ }
+
+ return path;
+}
+
+gchar *url_path_dirname(const gchar *url) {
+ if (!url)
+ return NULL;
+
+ const gchar *scheme_end = skip_scheme(url);
+ size_t path_len = strcspn(scheme_end, "?#");
+ const gchar *p = scheme_end + path_len;
+ while ((p >= url) && (*p != '/')) {
+ p--;
+ }
+
+ if (*p == '/') {
+ return g_strndup(url, (p+1) - url);
+ } else {
+ return g_strdup("/");
+ }
+}
+
+gchar *url_path_and_query(const gchar *url) {
+ if (!url)
+ return NULL;
+
+ const gchar *scheme_end = skip_scheme(url);
+ size_t path_len = strcspn(scheme_end, "#");
+ const gchar *end = scheme_end + path_len;
+ return g_strndup(url, end - url);
+}
+
+const gchar *skip_scheme(const char *url) {
+ const gchar *c = url;
+ while (is_scheme_character(*c)) {
+ c++;
+ }
+
+ if (strncmp(c, "://", 3) == 0) {
+ // scheme found
+ return c + 3;
+ } else {
+ // schemeless url
+ return url;
+ }
+}
+
+gboolean is_scheme_character(gchar c) {
+ return g_ascii_isalnum(c) || (c == '+') || (c == '-') || (c == '.');
+}
diff --git a/src/libwebvi/urlutils.h b/src/libwebvi/urlutils.h
new file mode 100644
index 0000000..374a941
--- /dev/null
+++ b/src/libwebvi/urlutils.h
@@ -0,0 +1,32 @@
+/*
+ * urlutils.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __URLUTILS_H
+#define __URLUTILS_H
+
+#include <glib.h>
+
+gchar *relative_url_to_absolute(const gchar *baseurl, const gchar *href);
+gchar *url_scheme(const gchar *baseurl);
+gchar *url_root(const gchar *baseurl);
+gchar *url_path_including_file(const gchar *baseurl);
+gchar *url_path_dirname(const gchar *baseurl);
+gchar *url_path_and_query(const gchar *baseurl);
+
+#endif // __URLUTILS_H
diff --git a/src/libwebvi/webvicontext.c b/src/libwebvi/webvicontext.c
new file mode 100644
index 0000000..4e33e94
--- /dev/null
+++ b/src/libwebvi/webvicontext.c
@@ -0,0 +1,409 @@
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gprintf.h>
+#include "webvicontext.h"
+#include "libwebvi.h"
+#include "request.h"
+
+#define DEFAULT_TEMPLATE_PATH "/etc/webvi/websites"
+#define MAX_MESSAGE_LENGTH 128
+
+struct WebviContext {
+ GTree *requests;
+ LinkTemplates *link_templates;
+ WebviHandle next_request;
+ CURLM *curl_multi_handle;
+ gchar *template_path;
+ GArray *finish_messages;
+ /* The value returned by the latest webvi_context_next_message() call */
+ WebviMsg current_message;
+ bool debug;
+};
+
+typedef struct RequestAndHandle {
+ const WebviRequest *request;
+ WebviHandle handle;
+} RequestAndHandle;
+
+typedef struct FoundFds {
+ fd_set *readfds;
+ fd_set *writefds;
+ fd_set *excfds;
+ int *max_fd;
+} FoundFds;
+
+typedef struct SocketToHandle {
+ int sockfd;
+ int ev_bitmask;
+ gboolean handled;
+} SocketToHandle;
+
+static WebviCtx handle_for_context(WebviContext *ctx);
+static gint cmp_int(gconstpointer a, gconstpointer b, gpointer user_data);
+static gboolean gather_fds(gpointer key, gpointer value, gpointer data);
+static gboolean handle_request_socket(gpointer key, gpointer value, gpointer data);
+static WebviHandle get_handle_for_request(WebviContext *ctx, const WebviRequest *req);
+static gboolean search_by_request(gpointer key, gpointer value, gpointer data);
+static void check_for_finished_curl(CURLM *multi_handle);
+static RequestState curl_code_to_pipe_state(CURLcode curlcode);
+static WebviResult curlmcode_to_webvierr(CURLMcode mcode);
+static void webvi_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer user_data);
+static void register_context(WebviCtx key, WebviContext *value);
+static GTree *get_tls_contexts();
+static void webvi_context_delete(WebviContext *ctx);
+static void free_tls_context(gpointer data);
+static void free_context(gpointer data);
+static void free_request(gpointer data);
+
+static GPrivate tls_contexts = G_PRIVATE_INIT(free_tls_context);
+
+WebviCtx webvi_context_initialize() {
+ WebviContext *ctx = malloc(sizeof(WebviContext));
+ if (!ctx) {
+ return 0;
+ }
+
+ memset(ctx, 0, sizeof(WebviContext));
+
+ ctx->requests = g_tree_new_full(cmp_int, NULL, NULL, free_request);
+ if (!ctx->requests) {
+ webvi_context_delete(ctx);
+ return 0;
+ }
+
+ ctx->finish_messages = g_array_new(FALSE, TRUE, sizeof(WebviMsg));
+ if (!ctx->finish_messages) {
+ webvi_context_delete(ctx);
+ return 0;
+ }
+
+ ctx->next_request = 1;
+
+ WebviCtx ctxhandle = handle_for_context(ctx);
+ register_context(ctxhandle, ctx);
+
+ return ctxhandle;
+}
+
+void register_context(WebviCtx ctxhandle, WebviContext *ctx) {
+ GTree *contexts = get_tls_contexts();
+ g_tree_insert(contexts, GINT_TO_POINTER(ctxhandle), ctx);
+}
+
+void webvi_context_cleanup(WebviCtx ctxhandle) {
+ GTree *contexts = get_tls_contexts();
+ g_tree_remove(contexts, GINT_TO_POINTER(ctxhandle));
+}
+
+void webvi_context_set_debug(WebviContext *self, bool d) {
+ GLogFunc logfunc;
+
+ self->debug = d;
+ if (self->debug) {
+ logfunc = webvi_log_handler;
+ } else {
+ logfunc = g_log_default_handler;
+ }
+
+ g_log_set_handler(LIBWEBVI_LOG_DOMAIN,
+ G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG,
+ logfunc, NULL);
+}
+
+void webvi_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer user_data)
+{
+ g_fprintf(stderr, "%s: %s\n", log_domain, message);
+}
+
+void webvi_context_set_template_path(WebviContext *self, const char *path) {
+ if (self->link_templates) {
+ link_templates_delete(self->link_templates);
+ self->link_templates = NULL;
+ }
+ if (self->template_path) {
+ g_free(self->template_path);
+ }
+ self->template_path = path ? g_strdup(path) : NULL;
+}
+
+const char *webvi_context_get_template_path(const WebviContext *self) {
+ return self->template_path ? self->template_path : DEFAULT_TEMPLATE_PATH;
+}
+
+const LinkTemplates *get_link_templates(WebviContext *self) {
+ if (!self->link_templates) {
+ self->link_templates = link_templates_create();
+ if (self->link_templates) {
+ const gchar *path = webvi_context_get_template_path(self);
+ gchar *template_file = g_strconcat(path, "/links", NULL);
+ link_templates_load(self->link_templates, template_file);
+ g_free(template_file);
+ }
+ }
+
+ return self->link_templates;
+}
+
+WebviHandle webvi_context_add_request(WebviContext *self, WebviRequest *req) {
+ int h = self->next_request++;
+ g_tree_insert(self->requests, GINT_TO_POINTER(h), req);
+ return (WebviHandle)h;
+}
+
+WebviRequest *webvi_context_get_request(WebviContext *self, WebviHandle h) {
+ return (WebviRequest *)g_tree_lookup(self->requests, GINT_TO_POINTER(h));
+}
+
+void webvi_context_remove_request(WebviContext *self, WebviHandle h) {
+ g_tree_remove(self->requests, GINT_TO_POINTER(h));
+}
+
+CURLM *webvi_context_get_curl_multi_handle(WebviContext *self) {
+ if (!self->curl_multi_handle)
+ self->curl_multi_handle = curl_multi_init();
+ return self->curl_multi_handle;
+}
+
+WebviCtx handle_for_context(WebviContext *ctx) {
+ return (WebviCtx)ctx; // FIXME
+}
+
+WebviContext *get_context_by_handle(WebviCtx handle) {
+ GTree *contexts = get_tls_contexts();
+ return g_tree_lookup(contexts, GINT_TO_POINTER(handle));
+}
+
+WebviResult webvi_context_fdset(WebviContext *ctx, fd_set *readfds,
+ fd_set *writefds, fd_set *excfds, int *max_fd)
+{
+ WebviResult res = WEBVIERR_OK;
+ *max_fd = -1;
+
+ // curl sockets
+ CURLM *mhandle = webvi_context_get_curl_multi_handle(ctx);
+ if (mhandle) {
+ CURLMcode mcode = curl_multi_fdset(mhandle, readfds, writefds, excfds, max_fd);
+ res = curlmcode_to_webvierr(mcode);
+ }
+
+ // non-curl fds
+ FoundFds fds;
+ fds.readfds = readfds;
+ fds.writefds = writefds;
+ fds.excfds = excfds;
+ fds.max_fd = max_fd;
+ g_tree_foreach(ctx->requests, gather_fds, &fds);
+
+ return res;
+}
+
+gboolean gather_fds(gpointer key, gpointer value, gpointer data) {
+ FoundFds *fds = (FoundFds *)data;
+ WebviRequest *req = (WebviRequest *)value;
+ request_fdset(req, fds->readfds, fds->writefds, fds->excfds, fds->max_fd);
+ return FALSE;
+}
+
+void webvi_context_handle_socket_action(
+ WebviContext *ctx, int sockfd, int ev_bitmask, long *running_handles)
+{
+ SocketToHandle x;
+ x.sockfd = sockfd;
+ x.ev_bitmask = ev_bitmask;
+ x.handled = FALSE;
+ g_tree_foreach(ctx->requests, handle_request_socket, &x);
+
+ int curl_handles = 0;
+ if (!x.handled) {
+ // sockfd belongs to curl
+ CURLM *multi_handle = webvi_context_get_curl_multi_handle(ctx);
+ if (multi_handle) {
+ curl_socket_t curl_socket;
+ int curl_mask = 0;
+
+ if (sockfd == WEBVI_SELECT_TIMEOUT) {
+ curl_socket = CURL_SOCKET_TIMEOUT;
+ curl_mask = 0;
+ } else {
+ curl_socket = sockfd;
+ if ((ev_bitmask & WEBVI_SELECT_READ) != 0)
+ curl_mask |= CURL_CSELECT_IN;
+ if ((ev_bitmask & WEBVI_SELECT_WRITE) != 0)
+ curl_mask |= CURL_CSELECT_OUT;
+ if ((ev_bitmask & WEBVI_SELECT_EXCEPTION) != 0)
+ curl_mask |= CURL_CSELECT_ERR;
+ }
+
+ curl_multi_socket_action(multi_handle, curl_socket, curl_mask, &curl_handles);
+ check_for_finished_curl(multi_handle);
+ }
+ }
+
+ // FIXME: running_handles
+ *running_handles = curl_handles;
+}
+
+gboolean handle_request_socket(gpointer key, gpointer value, gpointer data) {
+ WebviRequest *req = (WebviRequest *)value;
+ SocketToHandle *to_handle = (SocketToHandle *)data;
+ return request_handle_socket(req, to_handle->sockfd, to_handle->ev_bitmask);
+}
+
+void check_for_finished_curl(CURLM *multi_handle) {
+ int num_messages;
+ CURLMsg *info;
+ while ((info = curl_multi_info_read(multi_handle, &num_messages))) {
+ if (info->msg == CURLMSG_DONE) {
+ char *instance;
+ if (curl_easy_getinfo(info->easy_handle, CURLINFO_PRIVATE, &instance) == CURLE_OK) {
+ PipeComponent *pipe = (PipeComponent *)instance;
+ pipe_component_finished(pipe, curl_code_to_pipe_state(info->data.result));
+ }
+ }
+ }
+}
+
+RequestState curl_code_to_pipe_state(CURLcode curlcode) {
+ switch (curlcode) {
+ case CURLE_OK:
+ return WEBVISTATE_FINISHED_OK;
+
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_TOO_MANY_REDIRECTS:
+ case CURLE_GOT_NOTHING:
+ case CURLE_RECV_ERROR:
+ return WEBVISTATE_NETWORK_READ_ERROR;
+
+ case CURLE_REMOTE_FILE_NOT_FOUND:
+ return WEBVISTATE_NOT_FOUND;
+
+ case CURLE_OPERATION_TIMEDOUT:
+ return WEBVISTATE_TIMEDOUT;
+
+ default:
+ return WEBVISTATE_IO_ERROR;
+ }
+}
+
+WebviResult curlmcode_to_webvierr(CURLMcode mcode) {
+ switch (mcode) {
+ case CURLM_OK:
+ return WEBVIERR_OK;
+
+ case CURLM_BAD_HANDLE:
+ case CURLM_BAD_EASY_HANDLE:
+ return WEBVIERR_INVALID_PARAMETER;
+
+ default:
+ return WEBVIERR_UNKNOWN_ERROR;
+ };
+}
+
+void webvi_context_add_finished_message(WebviContext *ctx,
+ const WebviRequest *req,
+ RequestState status_code,
+ const char *message_text)
+{
+ WebviHandle h = get_handle_for_request(ctx, req);
+ if (h != 0) {
+ GArray *messages = ctx->finish_messages;
+ WebviMsg msg;
+ msg.msg = WEBVIMSG_DONE;
+ msg.handle = h;
+ msg.status_code = status_code;
+ msg.data = message_text;
+ g_array_append_val(messages, msg);
+ }
+}
+
+WebviHandle get_handle_for_request(WebviContext *ctx, const WebviRequest *req) {
+ RequestAndHandle query_and_result;
+ query_and_result.request = req;
+ query_and_result.handle = 0;
+ g_tree_foreach(ctx->requests, search_by_request, &query_and_result);
+ return query_and_result.handle;
+}
+
+gboolean search_by_request(gpointer key, gpointer value, gpointer data) {
+ WebviHandle h = GPOINTER_TO_INT(key);
+ WebviRequest *req = value;
+ RequestAndHandle *query_and_result = data;
+
+ if (query_and_result->request == req) {
+ query_and_result->handle = h;
+ return TRUE; /* stop traversal */
+ } else {
+ return FALSE;
+ }
+}
+
+WebviMsg *webvi_context_next_message(WebviContext *ctx, int *remaining_messages) {
+ guint len = ctx->finish_messages->len;
+ if (len > 0) {
+ if (remaining_messages)
+ *remaining_messages = (int)len-1;
+
+ ctx->current_message = g_array_index(ctx->finish_messages, WebviMsg, 0);
+ g_array_remove_index(ctx->finish_messages, 0);
+ return &ctx->current_message;
+ } else {
+ if (remaining_messages)
+ *remaining_messages = 0;
+ return NULL;
+ }
+}
+
+void webvi_context_delete(WebviContext *ctx) {
+ if (ctx) {
+ if (ctx->finish_messages)
+ g_array_free(ctx->finish_messages, TRUE);
+ if (ctx->curl_multi_handle)
+ curl_multi_cleanup(ctx->curl_multi_handle);
+ if (ctx->requests)
+ g_tree_unref(ctx->requests);
+ /* if (ctx->downloaders) */
+ /* g_ptr_array_unref(ctx->downloaders); */
+ if (ctx->link_templates)
+ link_templates_delete(ctx->link_templates);
+ g_free(ctx->template_path);
+
+ free(ctx);
+ }
+}
+
+GTree *get_tls_contexts() {
+ GTree *contexts = g_private_get(&tls_contexts);
+ if (!contexts) {
+ contexts = g_tree_new_full(cmp_int, NULL, NULL, free_context);
+ g_private_set(&tls_contexts, contexts);
+ }
+
+ return contexts;
+}
+
+void webvi_context_cleanup_all() {
+ /* This will cause free_tls_context to be called if tls_context was set */
+ g_private_set(&tls_contexts, NULL);
+}
+
+gint cmp_int(gconstpointer a, gconstpointer b, gpointer user_data) {
+ int aint = GPOINTER_TO_INT(a);
+ int bint = GPOINTER_TO_INT(b);
+ return aint - bint;
+}
+
+void free_tls_context(gpointer data) {
+ if (data) {
+ g_tree_destroy((GTree *)data);
+ }
+}
+
+void free_request(gpointer data) {
+ request_delete((WebviRequest *)data);
+}
+
+void free_context(gpointer data) {
+ webvi_context_delete((WebviContext *)data);
+}
diff --git a/src/libwebvi/webvicontext.h b/src/libwebvi/webvicontext.h
new file mode 100644
index 0000000..7fa0738
--- /dev/null
+++ b/src/libwebvi/webvicontext.h
@@ -0,0 +1,63 @@
+/*
+ * webvicontext.h
+ *
+ * Copyright (c) 2013 Antti Ajanki <antti.ajanki@iki.fi>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WEBVICONTEXT_H
+#define __WEBVICONTEXT_H
+
+#include <stdbool.h>
+#include <curl/curl.h>
+#include <glib.h>
+#include "libwebvi.h"
+#include "linktemplates.h"
+#include "pipecomponent.h"
+
+typedef struct WebviContext WebviContext;
+typedef struct WebviRequest WebviRequest;
+
+WebviContext *get_context_by_handle(WebviCtx handle);
+
+WebviCtx webvi_context_initialize(void);
+void webvi_context_cleanup(WebviCtx ctxhandle);
+void webvi_context_cleanup_all();
+
+void webvi_context_set_debug(WebviContext *self,
+ bool d);
+void webvi_context_set_template_path(WebviContext *self,
+ const char *path);
+const char *webvi_context_get_template_path(const WebviContext *self);
+const LinkTemplates *get_link_templates(WebviContext *self);
+CURLM *webvi_context_get_curl_multi_handle(WebviContext *self);
+
+WebviHandle webvi_context_add_request(WebviContext *self, WebviRequest *req);
+void webvi_context_remove_request(WebviContext *self, WebviHandle h);
+WebviRequest *webvi_context_get_request(WebviContext *self, WebviHandle h);
+
+WebviResult webvi_context_fdset(WebviContext *ctx, fd_set *readfd,
+ fd_set *writefd, fd_set *excfd, int *max_fd);
+void webvi_context_handle_socket_action(
+ WebviContext *ctx, int sockfd, int ev_bitmask, long *running_handles);
+
+void webvi_context_add_finished_message(WebviContext *messages,
+ const WebviRequest *req,
+ RequestState status_code,
+ const char *message_text);
+WebviMsg *webvi_context_next_message(WebviContext *ctx,
+ int *remaining_messages);
+
+#endif // __WEBVICONTEXT_H