diff options
-rw-r--r-- | src/libwebvi/libwebvi.c | 4 | ||||
-rw-r--r-- | src/libwebvi/libwebvi.h | 6 | ||||
-rw-r--r-- | src/libwebvi/link.c | 2 | ||||
-rw-r--r-- | src/libwebvi/link.h | 2 | ||||
-rw-r--r-- | src/libwebvi/linktemplates.c | 9 | ||||
-rw-r--r-- | src/libwebvi/menubuilder.c | 2 | ||||
-rw-r--r-- | src/libwebvi/pipecomponent.c | 223 | ||||
-rw-r--r-- | src/libwebvi/pipecomponent.h | 7 | ||||
-rw-r--r-- | src/libwebvi/request.c | 28 | ||||
-rw-r--r-- | src/libwebvi/webvicontext.c | 15 | ||||
-rw-r--r-- | src/libwebvi/webvicontext.h | 3 | ||||
-rwxr-xr-x | src/menuscripts/stream-areena | 14 | ||||
-rwxr-xr-x | src/menuscripts/stream-quvi | 19 | ||||
-rwxr-xr-x | src/menuscripts/utils.sh | 15 | ||||
-rw-r--r-- | src/pywebvi/pywebvi.py | 10 | ||||
-rw-r--r-- | src/webvicli/webvicli/client.py | 22 | ||||
-rw-r--r-- | tests/libwebvi_tests.c | 6 | ||||
-rw-r--r-- | tests/pipe_tests.c | 47 | ||||
-rw-r--r-- | tests/pipe_tests.h | 4 | ||||
-rw-r--r-- | websites/areena.yle.fi/menu.xml | 9 | ||||
-rw-r--r-- | websites/links | 35 |
21 files changed, 296 insertions, 186 deletions
diff --git a/src/libwebvi/libwebvi.c b/src/libwebvi/libwebvi.c index feeec28..cd6df13 100644 --- a/src/libwebvi/libwebvi.c +++ b/src/libwebvi/libwebvi.c @@ -101,6 +101,10 @@ WebviResult webvi_set_config(WebviCtx ctxhandle, WebviConfig conf, ...) { webvi_context_set_timeout_data(ctx, data); break; } + case WEBVI_CONFIG_MENU_SCRIPT_PATH: + p = va_arg(argptr, char *); + webvi_context_set_menu_script_path(ctx, p); + break; default: res = WEBVIERR_INVALID_PARAMETER; }; diff --git a/src/libwebvi/libwebvi.h b/src/libwebvi/libwebvi.h index 8efe953..74c9f6c 100644 --- a/src/libwebvi/libwebvi.h +++ b/src/libwebvi/libwebvi.h @@ -102,7 +102,8 @@ typedef enum { WEBVI_CONFIG_TEMPLATE_PATH, WEBVI_CONFIG_DEBUG, WEBVI_CONFIG_TIMEOUT_CALLBACK, - WEBVI_CONFIG_TIMEOUT_DATA + WEBVI_CONFIG_TIMEOUT_DATA, + WEBVI_CONFIG_MENU_SCRIPT_PATH } WebviConfig; typedef struct { @@ -167,6 +168,9 @@ LIBWEBVI_DLL_EXPORT const char* webvi_strerror(WebviResult err); * WEBVI_CONFIG_TEMPLATE_PATH * Set the base directory for the XSLT templates (char *) * + * WEBVI_CONFIG_MENU_SCRIPT_PATH + * Specify the directory where to look for the menu scripts (char *) + * * WEBVI_CONFIG_DEBUG * If value is not "0", print debug output to stdin (char *) * diff --git a/src/libwebvi/link.c b/src/libwebvi/link.c index 501e70a..fbce59b 100644 --- a/src/libwebvi/link.c +++ b/src/libwebvi/link.c @@ -73,7 +73,7 @@ struct ActionTypeMessage { 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_STREAM, "stream"}, {LINK_ACTION_EXTERNAL_COMMAND, "external command"}}; for (int i=0; i<(sizeof(messages)/sizeof(messages[0])); i++) { diff --git a/src/libwebvi/link.h b/src/libwebvi/link.h index 0ddc05c..9fac1f4 100644 --- a/src/libwebvi/link.h +++ b/src/libwebvi/link.h @@ -22,7 +22,7 @@ typedef enum { LINK_ACTION_PARSE, - LINK_ACTION_STREAM_LIBQUVI, + LINK_ACTION_STREAM, LINK_ACTION_EXTERNAL_COMMAND } LinkActionType; diff --git a/src/libwebvi/linktemplates.c b/src/libwebvi/linktemplates.c index a193df3..e7f76fd 100644 --- a/src/libwebvi/linktemplates.c +++ b/src/libwebvi/linktemplates.c @@ -126,15 +126,12 @@ LinkAction *parse_action(const gchar *actionstr) { 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, STREAM_SELECTOR, STREAM_SELECTOR_LEN) == 0) { + const gchar *command = actionstr + STREAM_SELECTOR_LEN; + return link_action_create(LINK_ACTION_STREAM, command); } 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); diff --git a/src/libwebvi/menubuilder.c b/src/libwebvi/menubuilder.c index d2b54ce..bffbc1e 100644 --- a/src/libwebvi/menubuilder.c +++ b/src/libwebvi/menubuilder.c @@ -67,7 +67,7 @@ void add_link_to_menu(gpointer data, gpointer instance) { void menu_builder_append_link(MenuBuilder *self, const Link *link) { const char *class; - if (link_get_type(link) == LINK_ACTION_STREAM_LIBQUVI) { + if (link_get_type(link) == LINK_ACTION_STREAM) { class = "stream"; } else { class = "webvi"; diff --git a/src/libwebvi/pipecomponent.c b/src/libwebvi/pipecomponent.c index 34e7634..7390bf3 100644 --- a/src/libwebvi/pipecomponent.c +++ b/src/libwebvi/pipecomponent.c @@ -52,8 +52,11 @@ struct PipeMainMenuDownloader { struct PipeExternalDownloader { PipeComponent pipe_data; + gchar *command; gchar *url; - int fd; + gchar *menu_script_path; + GPid pid; + gint fd; }; struct PipeLocalFile { @@ -62,11 +65,8 @@ struct PipeLocalFile { int fd; }; -struct PipeLibquvi { +struct PipeMenuValidator { PipeComponent pipe_data; - gchar *url; - GPid pid; - gint quvi_output; xmlParserCtxtPtr parser; }; @@ -108,15 +108,9 @@ 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 gboolean pipe_menu_validator_parse(PipeComponent *instance, char *buf, size_t len); +static void pipe_menu_validator_validate(PipeComponent *instance, RequestState state); +static void pipe_menu_validator_delete(PipeComponent *instance); static CURL *start_curl(const char *url, CURLM *curlmulti, PipeComponent *instance); @@ -173,7 +167,7 @@ void pipe_component_finished(PipeComponent *self, RequestState state) { if (self->finished) self->finished(self, state); if (self->next) - pipe_component_finished(self->next, state); + pipe_component_finished(self->next, self->state); } } @@ -470,14 +464,16 @@ void pipe_local_file_delete(PipeComponent *instance) { /***** PipeExternalDownloader *****/ -PipeExternalDownloader *pipe_external_downloader_create(const gchar *url, - const gchar *command) +PipeExternalDownloader *pipe_external_downloader_create( + const gchar *url, const gchar *command, const gchar *menu_script_path) { INITIALIZE_PIPE_WITH_FDSET(PipeExternalDownloader, NULL, NULL, pipe_external_downloader_delete, pipe_external_downloader_fdset, pipe_external_downloader_handle_socket); + self->command = g_strdup(command); self->url = g_strdup(url); + self->menu_script_path = g_strdup(menu_script_path); self->fd = -1; g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, @@ -487,8 +483,21 @@ PipeExternalDownloader *pipe_external_downloader_create(const gchar *url, } void pipe_external_downloader_start(PipeExternalDownloader *self) { - // FIXME - pipe_component_finished((PipeComponent *)self, WEBVISTATE_INTERNAL_ERROR); + GError *error = NULL; + gchar *argv[] = {self->command, self->url, NULL}; + /* gchar *envpath = g_strconcat("PATH=", LIBWEBVI_SCRIPT_PATH, */ + /* ":/usr/bin:/bin:/usr/local/bin", NULL); */ + /* gchar *envp[] = {envpath, NULL}; */ + g_spawn_async_with_pipes(self->menu_script_path, argv, NULL, 0, + NULL, NULL, &self->pid, NULL, &self->fd, + NULL, &error); + if (error) { + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_INFO, + "Executing '%s %s' failed: %s", self->command, self->url, + error->message); + pipe_component_finished((PipeComponent *)self, WEBVISTATE_SUBPROCESS_FAILED); + } + /* g_free(envpath); */ } void pipe_external_downloader_fdset(PipeComponent *instance, fd_set *readfd, @@ -507,164 +516,102 @@ gboolean pipe_external_downloader_handle_socket(PipeComponent *instance, void pipe_external_downloader_delete(PipeComponent *instance) { PipeExternalDownloader *self = (PipeExternalDownloader *)instance; - if (self->fd != -1) + if (self->fd != -1) { close(self->fd); + } + if (self->pid != -1) { + g_spawn_close_pid(self->pid); + } g_free(self->url); - g_free(self); + g_free(self->command); + g_free(self->menu_script_path); + free(self); } -/***** PipeLibquvi *****/ +/***** PipeMenuValidator *****/ -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; +PipeMenuValidator *pipe_menu_validator_create() { + INITIALIZE_PIPE(PipeMenuValidator, pipe_menu_validator_parse, + pipe_menu_validator_validate, + pipe_menu_validator_delete); 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; +gboolean pipe_menu_validator_parse(PipeComponent *instance, char *buf, size_t len) { + PipeMenuValidator *self = (PipeMenuValidator *)instance; if (!self->parser) { - self->parser = xmlCreatePushParserCtxt(NULL, NULL, buf, len, "quvioutput.xml"); - g_assert(self->parser); + self->parser = xmlCreatePushParserCtxt(NULL, NULL, buf, len, + "externalscript.xml"); + if (!self->parser) { + pipe_component_finished(instance, WEBVISTATE_MEMORY_ALLOCATION_ERROR); + } } else { - xmlParseChunk(self->parser, buf, len, 0); + int err = xmlParseChunk(self->parser, buf, len, 0); + if (err) { + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_INFO, + "Menu is not valid XML (libxml2 error %d)", err); + pipe_component_finished(instance, WEBVISTATE_SUBPROCESS_FAILED); + return FALSE; + } } - - return FALSE; + return TRUE; } -void pipe_libquvi_finished(PipeComponent *instance, RequestState state) { +void pipe_menu_validator_validate(PipeComponent *instance, RequestState state) { + PipeMenuValidator *self = (PipeMenuValidator *)instance; 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); + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "no menu to validate"); + return; + } + + int err = xmlParseChunk(self->parser, NULL, 0, 1); + if (err) { + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_INFO, + "Menu is not valid XML (xmlParserErrors %d)", err); + instance->state = WEBVISTATE_SUBPROCESS_FAILED; return; } - xmlParseChunk(self->parser, NULL, 0, 1); xmlDoc *doc = self->parser->myDoc; + if (!doc) { + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_INFO, + "XML parser returned no document"); + instance->state = WEBVISTATE_SUBPROCESS_FAILED; + return; + } xmlChar *dump; int dumpLen; xmlDocDumpMemory(doc, &dump, &dumpLen); - g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "quvi output:\n%s", dump); + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "validating menu:\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; + if (!xmlStrEqual(root->name, BAD_CAST "wvmenu")) { + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_INFO, + "Unexpected root node: %s", (char *)root->name); + instance->state = WEBVISTATE_SUBPROCESS_FAILED; + return; } - return NULL; + g_log(LIBWEBVI_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Menu is valid"); } -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); - } +void pipe_menu_validator_delete(PipeComponent *instance) { + PipeMenuValidator *self = (PipeMenuValidator *)instance; if (self->parser) { + if (self->parser->myDoc) { + xmlFreeDoc(self->parser->myDoc); + } xmlFreeParserCtxt(self->parser); } - g_free(self->url); free(self); } diff --git a/src/libwebvi/pipecomponent.h b/src/libwebvi/pipecomponent.h index 00a05ef..451cc96 100644 --- a/src/libwebvi/pipecomponent.h +++ b/src/libwebvi/pipecomponent.h @@ -47,6 +47,7 @@ typedef struct PipeMainMenuDownloader PipeMainMenuDownloader; typedef struct PipeLocalFile PipeLocalFile; typedef struct PipeExternalDownloader PipeExternalDownloader; typedef struct PipeLibquvi PipeLibquvi; +typedef struct PipeMenuValidator PipeMenuValidator; void pipe_component_initialize(PipeComponent *self, gboolean (*process_cb)(PipeComponent *, char *, size_t), @@ -85,11 +86,13 @@ PipeCallbackWrapper *pipe_callback_wrapper_create( void (*finish_callback)(RequestState, void *), void *finishdata); -PipeExternalDownloader *pipe_external_downloader_create(const gchar *url, - const gchar *command); +PipeExternalDownloader *pipe_external_downloader_create( + const gchar *url, const gchar *command, const gchar *menu_script_path); void pipe_external_downloader_start(PipeExternalDownloader *self); PipeLibquvi *pipe_libquvi_create(const gchar *url); void pipe_libquvi_start(PipeLibquvi *self); +PipeMenuValidator *pipe_menu_validator_create(); + #endif // __PIPECOMPONENT_H diff --git a/src/libwebvi/request.c b/src/libwebvi/request.c index af078c4..fc2b635 100644 --- a/src/libwebvi/request.c +++ b/src/libwebvi/request.c @@ -28,7 +28,6 @@ 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) { @@ -73,9 +72,8 @@ PipeComponent *pipe_factory(const WebviRequest *self) { 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) { + if ((action_type == LINK_ACTION_STREAM) || + (action_type == LINK_ACTION_EXTERNAL_COMMAND)) { const char *command = link_action_get_command(action); head = build_and_start_external_pipe(self, command); } else { @@ -108,26 +106,22 @@ PipeComponent *build_and_start_local_pipe(const WebviRequest *self) { 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( +PipeComponent *build_and_start_external_pipe(const WebviRequest *self, + const char *command) +{ + const char *path = webvi_context_get_menu_script_path(self->ctx); + PipeExternalDownloader *p1 = pipe_external_downloader_create( + self->url, command, path); + PipeComponent *p2 = (PipeComponent *)pipe_menu_validator_create(); + 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((PipeComponent *)p2, p3); 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); diff --git a/src/libwebvi/webvicontext.c b/src/libwebvi/webvicontext.c index 16cba10..c6c43a4 100644 --- a/src/libwebvi/webvicontext.c +++ b/src/libwebvi/webvicontext.c @@ -6,6 +6,7 @@ #include "request.h" #define DEFAULT_TEMPLATE_PATH "/etc/webvi/websites" +#define DEFAULT_MENU_SCRIPT_PATH "/usr/local/bin/webvi/menuscripts" #define MAX_MESSAGE_LENGTH 128 struct WebviContext { @@ -14,6 +15,7 @@ struct WebviContext { WebviHandle next_request; CURLM *curl_multi_handle; gchar *template_path; + gchar *menu_script_path; webvi_timeout_callback timeout_callback; void *timeout_data; GArray *finish_messages; @@ -128,13 +130,24 @@ void webvi_context_set_template_path(WebviContext *self, const char *path) { if (self->template_path) { g_free(self->template_path); } - self->template_path = path ? g_strdup(path) : NULL; + self->template_path = g_strdup(path); } const char *webvi_context_get_template_path(const WebviContext *self) { return self->template_path ? self->template_path : DEFAULT_TEMPLATE_PATH; } +void webvi_context_set_menu_script_path(WebviContext *self, const char *path) { + if (self->menu_script_path) { + g_free(self->menu_script_path); + } + self->menu_script_path = g_strdup(path); +} + +const char *webvi_context_get_menu_script_path(const WebviContext *self) { + return self->menu_script_path ? self->menu_script_path : DEFAULT_MENU_SCRIPT_PATH; +} + const LinkTemplates *get_link_templates(WebviContext *self) { if (!self->link_templates) { self->link_templates = link_templates_create(); diff --git a/src/libwebvi/webvicontext.h b/src/libwebvi/webvicontext.h index b3beb0e..210d940 100644 --- a/src/libwebvi/webvicontext.h +++ b/src/libwebvi/webvicontext.h @@ -41,6 +41,9 @@ void webvi_context_set_debug(WebviContext *self, void webvi_context_set_template_path(WebviContext *self, const char *path); const char *webvi_context_get_template_path(const WebviContext *self); +void webvi_context_set_menu_script_path(WebviContext *self, + const char *path); +const char *webvi_context_get_menu_script_path(const WebviContext *self); const LinkTemplates *get_link_templates(WebviContext *self); CURLM *webvi_context_get_curl_multi_handle(WebviContext *self); void webvi_context_set_timeout_callback(WebviContext *ctx, diff --git a/src/menuscripts/stream-areena b/src/menuscripts/stream-areena new file mode 100755 index 0000000..7b0c41f --- /dev/null +++ b/src/menuscripts/stream-areena @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +. utils.sh + +if [ "x$1" = "x" ]; then + exit 1 +fi + +STREAMURL=$(yle-dl --showurl "$1") +STREAMTITLE=$(yle-dl --showtitle "$1") + +outputWebviMenu "$STREAMURL" "$STREAMTITLE" diff --git a/src/menuscripts/stream-quvi b/src/menuscripts/stream-quvi new file mode 100755 index 0000000..b1d7662 --- /dev/null +++ b/src/menuscripts/stream-quvi @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +. utils.sh + +if [ "x$1" = "x" ]; then + exit 1 +fi + +Q=$(quvi "$1") +if [ "x$Q" = "x" ]; then + exit 1 +fi + +STREAMURL=$(echo "$Q" | sed -n 's/.*"url": "\(.*\)".*/\1/p' | head -n1) +STREAMTITLE=$(echo "$Q" | sed -n 's/.*"page_title": "\(.*\)".*/\1/p' | head -n1) + +outputWebviMenu "$STREAMURL" "$STREAMTITLE" diff --git a/src/menuscripts/utils.sh b/src/menuscripts/utils.sh new file mode 100755 index 0000000..0be2324 --- /dev/null +++ b/src/menuscripts/utils.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +function XMLEscape() { + echo "$1" | sed "s/\&/\&/g;s/>/\>/g;s/</\</g;s/'/\'/g" +} + +function outputWebviMenu() { + local url=$(XMLEscape "$1") + local title=$(XMLEscape "$2") + echo '<?xml version="1.0" encoding="UTF-8"?>' + echo -n '<wvmenu><title>'"$title"'</title>' + echo -n '<ul><li>' + echo -n '<a href="'"$url"'">'"$title"'</a>' + echo '</li></ul></wvmenu>' +} diff --git a/src/pywebvi/pywebvi.py b/src/pywebvi/pywebvi.py index b3df416..3815d0f 100644 --- a/src/pywebvi/pywebvi.py +++ b/src/pywebvi/pywebvi.py @@ -20,6 +20,7 @@ _WEBVI_CONFIG_TEMPLATE_PATH = 0 _WEBVI_CONFIG_DEBUG = 1 _WEBVI_CONFIG_TIMEOUT_CALLBACK = 2 _WEBVI_CONFIG_TIMEOUT_DATA = 3 +_WEBVI_CONFIG_MENU_SCRIPT_PATH = 4 class WebviState: NOT_FINISHED = 0 @@ -107,6 +108,15 @@ class WebviContext: set_config.restype = raise_if_webvi_result_not_ok set_config(self.handle, _WEBVI_CONFIG_TEMPLATE_PATH, path) + def set_menu_script_path(self, path): + if self.handle is None: + return + + set_config = libwebvi.webvi_set_config + set_config.argtypes = [c_long, c_int, c_char_p] + set_config.restype = raise_if_webvi_result_not_ok + set_config(self.handle, _WEBVI_CONFIG_MENU_SCRIPT_PATH, path) + def set_debug(self, enabled): if self.handle is None: return diff --git a/src/webvicli/webvicli/client.py b/src/webvicli/webvicli/client.py index 0af470d..9247fa1 100644 --- a/src/webvicli/webvicli/client.py +++ b/src/webvicli/webvicli/client.py @@ -106,6 +106,11 @@ def next_available_file_name(basename, ext): i += 1 return '%s-%d%s' % (basename, i, ext) +def is_rtmp_url(url): + RTMP_SCHEMES = ['rtmp://', 'rtmpe://', 'rtmps://', + 'rtmpt://', 'rtmpte://', 'rtmpts://'] + return any(url.startswith(x) for x in RTMP_SCHEMES) + class WebviURLopener(urllib.FancyURLopener): version = WEBVI_STREAM_USER_AGENT @@ -242,7 +247,10 @@ class WVClient: def set_template_path(self, path): self.webvi.set_template_path(path) - + + def set_menu_script_path(self, path): + self.webvi.set_menu_script_path(path) + def update_timeout(self, timeout_ms): if timeout_ms < 0: self.alarm = None @@ -429,6 +437,10 @@ class WVClient: return (menu[0].activate(), menu[0].label) def download_stream(self, url, title): + if is_rtmp_url(url): + print 'FIXME: downloading RTMP stream' + return + try: (tmpfilename, headers) = \ urllib.urlretrieve(url, reporthook=dl_progress) @@ -757,6 +769,10 @@ def parse_command_line(cmdlineargs, options): dest='templatepath', help='read video site templates from DIR', metavar='DIR', default=None) + parser.add_option('-m', '--menuscriptpath', type='string', + dest='menuscriptpath', + help='read menu scripts from DIR', metavar='DIR', + default=None) parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose', help='debug output', default=0) parser.add_option('--vfat', action='store_true', @@ -770,6 +786,8 @@ def parse_command_line(cmdlineargs, options): if cmdlineopt.templatepath is not None: options['templatepath'] = cmdlineopt.templatepath + if cmdlineopt.menuscriptpath is not None: + options['menuscriptpath'] = cmdlineopt.menuscriptpath if cmdlineopt.verbose > 0: options['verbose'] = cmdlineopt.verbose if cmdlineopt.vfat: @@ -815,6 +833,8 @@ def main(argv): client.set_debug(options['verbose']) if options.has_key('templatepath'): client.set_template_path(options['templatepath']) + if options.has_key('menuscriptpath'): + client.set_menu_script_path(options['menuscriptpath']) if options.has_key('url'): stream = options['url'] diff --git a/tests/libwebvi_tests.c b/tests/libwebvi_tests.c index 0e50e15..e1168a8 100644 --- a/tests/libwebvi_tests.c +++ b/tests/libwebvi_tests.c @@ -70,6 +70,12 @@ int main(int argc, char** argv) test_pipe_state_not_chaning_after_finished); g_test_add_func("/pipe/fdset", test_pipe_fdset); g_test_add_func("/pipe/delete_all", test_pipe_delete_all); + g_test_add_func("/pipe/menu_validator_valid_menu", + test_pipe_menu_validator_valid_menu); + g_test_add_func("/pipe/menu_validator_invalid_xml", + test_pipe_menu_validator_invalid_xml); + g_test_add_func("/pipe/menu_validator_invalid_root", + test_pipe_menu_validator_invalid_root); g_test_add_func("/urlutils/scheme", test_url_scheme); g_test_add_func("/urlutils/scheme_no_scheme", test_url_scheme_no_scheme); diff --git a/tests/pipe_tests.c b/tests/pipe_tests.c index 0dd15ba..d61b680 100644 --- a/tests/pipe_tests.c +++ b/tests/pipe_tests.c @@ -8,6 +8,12 @@ #define TEST_FD1 1 #define TEST_FD2 2 +#define MENU_VALID "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \ + "<wvmenu><title>Test menu</title></wvmenu>" +#define INVALID_XML ">> this & is not XML >>" +#define MENU_INVALID_ROOT "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \ + "<invalid></invalid>" + typedef struct CountingPipe { PipeComponent p; size_t bytes; @@ -244,3 +250,44 @@ void testpipe_delete2(PipeComponent *component) { delete2_called = TRUE; free(self); } + +void test_pipe_menu_validator_valid_menu() { + PipeMenuValidator *pipe = pipe_menu_validator_create(); + g_assert(pipe); + + pipe_component_append((PipeComponent *)pipe, MENU_VALID, + strlen(MENU_VALID)); + pipe_component_finished((PipeComponent *)pipe, WEBVISTATE_FINISHED_OK); + RequestState state = pipe_component_get_state((PipeComponent *)pipe); + g_assert(state == WEBVISTATE_FINISHED_OK); + + pipe_delete_full((PipeComponent *)pipe); +} + +void test_pipe_menu_validator_invalid_xml() { + PipeMenuValidator *pipe = pipe_menu_validator_create(); + g_assert(pipe); + + pipe_component_append((PipeComponent *)pipe, INVALID_XML, + strlen(INVALID_XML)); + pipe_component_finished((PipeComponent *)pipe, WEBVISTATE_FINISHED_OK); + RequestState state = pipe_component_get_state((PipeComponent *)pipe); + g_assert((state != WEBVISTATE_NOT_FINISHED) && + (state != WEBVISTATE_FINISHED_OK)); + + pipe_delete_full((PipeComponent *)pipe); +} + +void test_pipe_menu_validator_invalid_root() { + PipeMenuValidator *pipe = pipe_menu_validator_create(); + g_assert(pipe); + + pipe_component_append((PipeComponent *)pipe, MENU_INVALID_ROOT, + strlen(MENU_INVALID_ROOT)); + pipe_component_finished((PipeComponent *)pipe, WEBVISTATE_FINISHED_OK); + RequestState state = pipe_component_get_state((PipeComponent *)pipe); + g_assert((state != WEBVISTATE_NOT_FINISHED) && + (state != WEBVISTATE_FINISHED_OK)); + + pipe_delete_full((PipeComponent *)pipe); +} diff --git a/tests/pipe_tests.h b/tests/pipe_tests.h index 4ed02c2..0f0cf26 100644 --- a/tests/pipe_tests.h +++ b/tests/pipe_tests.h @@ -9,4 +9,8 @@ void test_pipe_state_not_chaning_after_finished(); void test_pipe_delete_all(); void test_pipe_fdset(); +void test_pipe_menu_validator_valid_menu(); +void test_pipe_menu_validator_invalid_xml(); +void test_pipe_menu_validator_invalid_root(); + #endif // PIPE_TESTS_H diff --git a/websites/areena.yle.fi/menu.xml b/websites/areena.yle.fi/menu.xml new file mode 100644 index 0000000..eb2d449 --- /dev/null +++ b/websites/areena.yle.fi/menu.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wvmenu> + <title>YLE Areena</title> + <description></description> + <ul> + <li><a href="http://areena.yle.fi/tv/" class="webvi">TV</a></li> + <li><a href="http://areena.yle.fi/radio/" class="webvi">Radio</a></li> + </ul> +</wvmenu> diff --git a/websites/links b/websites/links index 1618c9a..fb3a68b 100644 --- a/websites/links +++ b/websites/links @@ -11,7 +11,7 @@ http://www\.youtube\.com/playlist\?list= # search results http://www\.youtube\.com/results\? # video -http://www\.youtube\.com/watch\? stream:libquvi +http://www\.youtube\.com/watch\? stream:stream-quvi ### Metacafe ### @@ -20,7 +20,7 @@ http://www\.metacafe\.com/videos/ # search results ###http://www\.metacafe\.com/topics/ # video -http://www\.metacafe\.com/watch/ stream:libquvi +http://www\.metacafe\.com/watch/ stream:stream-quvi ### www.ruutu.fi ### @@ -29,7 +29,7 @@ http://www\.ruutu\.fi/ohjelmat/.+?/$ # search results http://www\.ruutu\.fi/hakutulokset/ # video -http://www\.ruutu\.fi/ohjelmat/.+?/.+ stream:libquvi +http://www\.ruutu\.fi/ohjelmat/.+?/.+ stream:stream-quvi ### Vimeo ### @@ -40,8 +40,8 @@ http://vimeo\.com/categories/[^/]+$ # group http://vimeo\.com/groups/[^/]+$ # video -http://vimeo\.com/[0-9]+$ stream:libquvi -http://vimeo\.com/.*/[0-9]+$ stream:libquvi +http://vimeo\.com/[0-9]+$ stream:stream-quvi +http://vimeo\.com/.*/[0-9]+$ stream:stream-quvi ### Dailymotion ### @@ -49,21 +49,22 @@ http://vimeo\.com/.*/[0-9]+$ stream:libquvi http://www\.dailymotion\.com/en/channel/ http://www\.dailymotion\.com/channel/ # video -http://www\.dailymotion\.com/video/ stream:libquvi +http://www\.dailymotion\.com/video/ stream:stream-quvi ### imdb ### # video -http://www\.imdb\.com/video/ stream:libquvi +http://www\.imdb\.com/video/ stream:stream-quvi ### areena.yle.fi ### +# video +http://areena\.yle\.fi/(tv|radio)/[0-9]+$ stream:stream-areena # category -http://areena\.yle\.fi/(?:tv|radio)/?.*/kaikki.json? bin:areena-category.py +http://areena\.yle\.fi/(tv|radio)/.*[a-z].*$ bin:areena-category.py +#http://areena\.yle\.fi/(tv|radio)/?.*/kaikki.json? bin:areena-category.py # search results -http://areena.yle.fi/.json bin:areena-category.py -# video -http://areena\.yle\.fi/(?:tv|radio)/[0-9]+$ stream:yle-dl +#http://areena.yle.fi/.json bin:areena-category.py ### ted.com ### @@ -72,28 +73,28 @@ http://www\.ted\.com/topics # playlists http://www\.ted\.com/playlists # video -http://www\.ted\.com/talks/ stream:libquvi +http://www\.ted\.com/talks/ stream:stream-quvi ### funnyordie.com ### -http://www\.funnyordie\.com/videos/[0-9a-f]{10}/ stream:libquvi +http://www\.funnyordie\.com/videos/[0-9a-f]{10}/ stream:stream-quvi ### videobash.com ### -http://www\.videobash\.com/video_show/ stream:libquvi +http://www\.videobash\.com/video_show/ stream:stream-quvi http://www\.videobash\.com/videos/ ### wimp.com ### -http://www\.wimp\.com/. stream:libquvi +http://www\.wimp\.com/. stream:stream-quvi ### 101greatgoals.com ### -http://www\.101greatgoals\.com/gvideos/ stream:libquvi +http://www\.101greatgoals\.com/gvideos/ stream:stream-quvi ### gaskrank.tv ### http://www\.gaskrank\.tv/tv/[^/]+/$ # page links http://www\.gaskrank\.tv/tv/[^/]+/filme-[0-9]+\.htm$ -http://www\.gaskrank\.tv/tv/.+\.htm$ stream:libquvi +http://www\.gaskrank\.tv/tv/.+\.htm$ stream:stream-quvi |