Add AJAX mailboxes for updating content asynchornously on the web ui.

This commit is contained in:
Andreas Öman 2008-04-16 05:23:03 +00:00
parent c2f176f588
commit ff70e88880
14 changed files with 474 additions and 17 deletions

View file

@ -2,7 +2,8 @@
SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \
subscriptions.c mux.c tsdemux.c buffer.c tcp.c \
resolver.c tsmux.c parsers.c bitstream.c parser_h264.c spawn.c
resolver.c tsmux.c parsers.c bitstream.c parser_h264.c spawn.c \
notify.c
SRCS += http.c
@ -33,7 +34,7 @@ SRCS += FFdecsa.c
#
VPATH += ajaxui
SRCS += ajaxui.c ajaxui_channels.c \
SRCS += ajaxui.c ajaxui_mailbox.c ajaxui_channels.c \
ajaxui_config.c ajaxui_config_channels.c ajaxui_config_dvb.c \
ajaxui_config_transport.c

View file

@ -26,6 +26,7 @@
#include "tvhead.h"
#include "http.h"
#include "ajaxui.h"
#include "dispatch.h"
#include "obj/ajaxui.cssh"
@ -93,7 +94,7 @@ ajax_table_header(http_connection_t *hc, tcp_queue_t *tq,
*/
void
ajax_table_row(tcp_queue_t *tq, const char *cells[], int columnsizes[],
int *bgptr)
int *bgptr, const char *idprefix[], const char *idpostfix)
{
int i = 0;
@ -103,7 +104,13 @@ ajax_table_row(tcp_queue_t *tq, const char *cells[], int columnsizes[],
*bgptr = !*bgptr;
while(cells[i]) {
tcp_qprintf(tq, "<div style=\"float: left; width: %d%%\">%s</div>",
tcp_qprintf(tq,
"<div %s%s%s%s%sstyle=\"float: left; width: %d%%\">%s</div>",
idprefix && idprefix[i] ? "id=\"" : "",
idprefix && idprefix[i] ? idprefix[i] : "",
idprefix && idprefix[i] && idpostfix ? "_" : "",
idprefix && idprefix[i] && idpostfix ? idpostfix : "",
idprefix && idprefix[i] ? "\" " : "",
columnsizes[i], cells[i]);
i++;
}
@ -399,21 +406,29 @@ ajax_page_root(http_connection_t *hc, http_reply_t *hr,
"<body>");
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div style=\"float: left; width: 80%\">");
ajax_box_begin(tq, AJAX_BOX_FILLED, "topmenu", NULL, NULL);
ajax_box_end(tq, AJAX_BOX_FILLED);
tcp_qprintf(tq, "<div id=\"topdeck\"></div>");
ajax_js(tq, "switchtab('top', '0')");
#if 0
tcp_qprintf(tq, "</div><div style=\"float: left; width: 20%\">");
tcp_qprintf(tq, "</body></html>");
ajax_box_begin(tq, AJAX_BOX_SIDEBOX, "statusbox", NULL, "System status");
ajax_box_end(tq, AJAX_BOX_SIDEBOX);
#endif
tcp_qprintf(tq, "</div></div></body></html>");
http_output_html(hc, hr);
return 0;
}
/**
* AJAX user interface
*/
@ -425,7 +440,6 @@ ajaxui_start(void)
http_path_add("/ajax/topmenu", NULL, ajax_page_titlebar);
http_path_add("/ajax/toptab", NULL, ajax_page_tab);
/* Stylesheet */
http_resource_add("/ajax/ajaxui.css", embedded_ajaxui,
sizeof(embedded_ajaxui), "text/css", "gzip");
@ -463,6 +477,7 @@ ajaxui_start(void)
http_resource_add("/gfx/mapped.png", embedded_mapped,
sizeof(embedded_mapped), "image/png", NULL);
ajax_mailbox_init();
ajax_channels_init();
ajax_config_init();
ajax_config_transport_init();

View file

@ -19,7 +19,6 @@
#ifndef AJAXUI_H_
#define AJAXUI_H_
typedef enum {
AJAX_BOX_FILLED,
AJAX_BOX_SIDEBOX,
@ -46,6 +45,8 @@ TAILQ_HEAD(ajax_menu_entry_queue, ajax_menu_entry);
void ajaxui_start(void);
void ajax_channels_init(void);
void ajax_config_init(void);
void ajax_mailbox_init(void);
int ajax_mailbox_create(char *subscriptionid);
void ajax_menu_bar_from_array(tcp_queue_t *tq, const char *name,
@ -74,6 +75,6 @@ void ajax_table_header(http_connection_t *hc, tcp_queue_t *tq,
int scrollbar, int columnsizes[]);
void ajax_table_row(tcp_queue_t *tq, const char *cells[], int columnsizes[],
int *bgptr);
int *bgptr, const char *idprefix[], const char *idpostfix);
#endif /* AJAXUI_H_ */

View file

@ -223,7 +223,7 @@ ajax_adaptereditor(http_connection_t *hc, http_reply_t *hr,
ajax_js(tq,
"new Ajax.Updater('dvbmuxlist%s', "
"'/ajax/dvbadaptermuxlist/%s', {method: 'get'}) ",
"'/ajax/dvbadaptermuxlist/%s', {method: 'get', evalScripts: true})",
tda->tda_identifier, tda->tda_identifier);
tcp_qprintf(tq, "<hr><div id=\"addmux\">");
@ -516,7 +516,7 @@ ajax_adaptercreatemux(http_connection_t *hc, http_reply_t *hr,
ajax_js(tq,
"new Ajax.Updater('dvbmuxlist%s', "
"'/ajax/dvbadaptermuxlist/%s', {method: 'get'}) ",
"'/ajax/dvbadaptermuxlist/%s', {method: 'get', evalScripts: true})",
tda->tda_identifier, tda->tda_identifier);
http_output_html(hc, hr);
@ -586,9 +586,10 @@ ajax_adaptermuxlist(http_connection_t *hc, http_reply_t *hr,
"'/ajax/dvbmuxeditor/%s', {method: 'get', evalScripts: true})\""
">%s</a>", tdmi->tdmi_identifier, buf);
cells[0] = buf2;
cells[1] = dvb_mux_status(tdmi);
switch(tdmi->tdmi_state) {
case TDMI_IDLE: txt = "Idle"; break;
case TDMI_IDLESCAN: txt = "Scanning"; break;
@ -597,7 +598,7 @@ ajax_adaptermuxlist(http_connection_t *hc, http_reply_t *hr,
}
cells[2] = txt;
txt = tdmi->tdmi_network;
if(txt == NULL)
txt = "Unknown";
@ -614,10 +615,14 @@ ajax_adaptermuxlist(http_connection_t *hc, http_reply_t *hr,
cells[4] = buf3;
cells[5] = NULL;
ajax_table_row(tq, cells, csize, &o);
ajax_table_row(tq, cells, csize, &o,
(const char *[]){NULL, "status", "state", "name", NULL},
tdmi->tdmi_identifier);
}
tcp_qprintf(tq, "</div>");
ajax_js(tq, "new Ajax.Request('/ajax/mailbox/%d')",
ajax_mailbox_create(tda->tda_identifier));
http_output_html(hc, hr);
return 0;

307
ajaxui/ajaxui_mailbox.c Normal file
View file

@ -0,0 +1,307 @@
/*
* tvheadend, AJAX / HTML Mailboxes
* Copyright (C) 2008 Andreas Öman
*
* 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 <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "tvhead.h"
#include "dispatch.h"
#include "http.h"
#include "ajaxui.h"
#define MAILBOX_UNUSED_TIMEOUT 15
#define MAILBOX_EMPTY_REPLY_TIMEOUT 10
static LIST_HEAD(, ajaxui_mailbox) mailboxes;
int mailbox_tally;
TAILQ_HEAD(ajaxui_letter_queue, ajaxui_letter);
typedef struct ajaxui_letter {
TAILQ_ENTRY(ajaxui_letter) al_link;
char *al_payload;
} ajaxui_letter_t;
typedef struct ajaxui_mailbox {
LIST_ENTRY(ajaxui_mailbox) amb_link;
uint32_t amb_boxid;
char *amb_subscriptionid;
dtimer_t amb_timer;
http_reply_t *amb_hr; /* Pending request */
struct ajaxui_letter_queue amb_letters;
} ajaxui_mailbox_t;
/**
*
*/
static void
al_destroy(ajaxui_mailbox_t *amb, ajaxui_letter_t *al)
{
TAILQ_REMOVE(&amb->amb_letters, al, al_link);
free(al);
}
/**
*
*/
static void
amb_destroy(ajaxui_mailbox_t *amb)
{
ajaxui_letter_t *al;
while((al = TAILQ_FIRST(&amb->amb_letters)) != NULL)
al_destroy(amb, al);
LIST_REMOVE(amb, amb_link);
dtimer_disarm(&amb->amb_timer);
free(amb->amb_subscriptionid);
free(amb);
}
/**
*
*/
static void
ajax_mailbox_unused(void *opaque, int64_t now)
{
ajaxui_mailbox_t *amb = opaque;
assert(amb->amb_hr == NULL);
amb_destroy(amb);
}
/**
*
*/
static void
ajax_mailbox_connection_lost(http_reply_t *hr, void *opaque)
{
ajaxui_mailbox_t *amb = opaque;
assert(hr == amb->amb_hr);
amb_destroy(amb);
}
/**
*
*/
int
ajax_mailbox_create(char *subscriptionid)
{
ajaxui_mailbox_t *amb = calloc(1, sizeof(ajaxui_mailbox_t));
mailbox_tally++;
amb->amb_boxid = mailbox_tally;
amb->amb_subscriptionid = strdup(subscriptionid);
LIST_INSERT_HEAD(&mailboxes, amb, amb_link);
TAILQ_INIT(&amb->amb_letters);
dtimer_arm(&amb->amb_timer, ajax_mailbox_unused, amb,
MAILBOX_UNUSED_TIMEOUT);
return amb->amb_boxid;
}
/**
*
*/
static void
ajax_mailbox_reply(ajaxui_mailbox_t *amb, http_reply_t *hr)
{
ajaxui_letter_t *al;
while((al = TAILQ_FIRST(&amb->amb_letters)) != NULL) {
tcp_qprintf(&hr->hr_tq, "%s", al->al_payload);
al_destroy(amb, al);
}
tcp_qprintf(&hr->hr_tq, "new Ajax.Request('/ajax/mailbox/%d');\r\n",
amb->amb_boxid);
http_output(hr->hr_connection, hr, "text/javascript", NULL, 0);
amb->amb_hr = NULL;
dtimer_arm(&amb->amb_timer, ajax_mailbox_unused, amb,
MAILBOX_UNUSED_TIMEOUT);
}
static void
ajax_mailbox_empty_reply(void *opaque, int64_t now)
{
ajaxui_mailbox_t *amb = opaque;
http_reply_t *hr = amb->amb_hr;
ajax_mailbox_reply(amb, hr);
http_continue(hr->hr_connection);
}
/**
* Poll callback
*
* Prepare the mailbox for reply
*/
static int
ajax_mailbox_poll(http_connection_t *hc, http_reply_t *hr,
const char *remain, void *opaque)
{
uint32_t boxid;
ajaxui_mailbox_t *amb;
if(remain == NULL)
return HTTP_STATUS_NOT_FOUND;
boxid = atoi(remain);
LIST_FOREACH(amb, &mailboxes, amb_link)
if(amb->amb_boxid == boxid)
break;
if(amb == NULL)
return HTTP_STATUS_NOT_FOUND;
if(amb->amb_hr != NULL)
return 409;
if(TAILQ_FIRST(&amb->amb_letters) != NULL) {
/* Pending letters, direct reply */
ajax_mailbox_reply(amb, hr);
return 0;
}
amb->amb_hr = hr;
hr->hr_opaque = amb;
hr->hr_destroy = ajax_mailbox_connection_lost;
dtimer_arm(&amb->amb_timer, ajax_mailbox_empty_reply, amb,
MAILBOX_EMPTY_REPLY_TIMEOUT);
return 0;
}
/**
*
*/
void
ajax_mailbox_init(void)
{
http_path_add("/ajax/mailbox", NULL, ajax_mailbox_poll);
}
/**
*
*/
static void
ajax_mailbox_add_to_subscription(const char *subscription, const char *content)
{
ajaxui_mailbox_t *amb;
ajaxui_letter_t *al;
http_connection_t *hc;
LIST_FOREACH(amb, &mailboxes, amb_link) {
if(strcmp(subscription, amb->amb_subscriptionid))
continue;
al = malloc(sizeof(ajaxui_letter_t));
al->al_payload = strdup(content);
TAILQ_INSERT_TAIL(&amb->amb_letters, al, al_link);
if(amb->amb_hr != NULL) {
hc = amb->amb_hr->hr_connection;
ajax_mailbox_reply(amb, amb->amb_hr);
http_continue(hc);
}
}
}
/**
*
*/
static void
ajax_mailbox_update_div(const char *subscription, const char *prefix,
const char *postfix, const char *content)
{
char buf[1000];
snprintf(buf, sizeof(buf),
"document.getElementById('%s_%s').innerHTML='%s';\r\n",
prefix, postfix, content);
ajax_mailbox_add_to_subscription(subscription, buf);
}
void
ajax_mailbox_tdmi_state_change(th_dvb_mux_instance_t *tdmi)
{
const char *txt;
switch(tdmi->tdmi_state) {
case TDMI_IDLE: txt = "Idle"; break;
case TDMI_IDLESCAN: txt = "Scanning"; break;
case TDMI_RUNNING: txt = "Running"; break;
default: txt = "???"; break;
}
ajax_mailbox_update_div(tdmi->tdmi_adapter->tda_identifier,
"state", tdmi->tdmi_identifier,
txt);
}
void
ajax_mailbox_tdmi_name_change(th_dvb_mux_instance_t *tdmi)
{
ajax_mailbox_update_div(tdmi->tdmi_adapter->tda_identifier,
"name", tdmi->tdmi_identifier,
tdmi->tdmi_network ?: "<noname>");
}
void
ajax_mailbox_tdmi_status_change(th_dvb_mux_instance_t *tdmi)
{
ajax_mailbox_update_div(tdmi->tdmi_adapter->tda_identifier,
"status", tdmi->tdmi_identifier,
tdmi->tdmi_last_status);
}

28
ajaxui/ajaxui_mailbox.h Normal file
View file

@ -0,0 +1,28 @@
/*
* tvheadend, AJAX user interface
* Copyright (C) 2007 Andreas Öman
*
* 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 <htmlui://www.gnu.org/licenses/>.
*/
#ifndef AJAXUI_MAILBOX_H_
#define AJAXUI_MAILBOX_H_
void ajax_mailbox_tdmi_state_change(th_dvb_mux_instance_t *tdmi);
void ajax_mailbox_tdmi_name_change(th_dvb_mux_instance_t *tdmi);
void ajax_mailbox_tdmi_status_change(th_dvb_mux_instance_t *tdmi);
#endif /* AJAXUI_MAILBOX_H_ */

BIN
dvb.c

Binary file not shown.

View file

@ -39,6 +39,7 @@
#include "dvb.h"
#include "dvb_support.h"
#include "diseqc.h"
#include "notify.h"
typedef struct dvb_fe_cmd {
TAILQ_ENTRY(dvb_fe_cmd) link;
@ -200,6 +201,7 @@ tdmi_stop(th_dvb_mux_instance_t *tdmi)
pthread_mutex_unlock(&tdmi->tdmi_table_lock);
tdmi->tdmi_state = TDMI_IDLE;
notify_tdmi_state_change(tdmi);
time(&tdmi->tdmi_lost_adapter);
}
@ -214,7 +216,10 @@ dvb_tune_tdmi(th_dvb_mux_instance_t *tdmi, int maylog, tdmi_state_t state)
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
char buf[100];
tdmi->tdmi_state = state;
if(tdmi->tdmi_state != state) {
tdmi->tdmi_state = state;
notify_tdmi_state_change(tdmi);
}
if(tda->tda_mux_current == tdmi)
return;

View file

@ -41,6 +41,7 @@
#include "transports.h"
#include "channels.h"
#include "psi.h"
#include "notify.h"
#define TDT_NOW 0x1
@ -614,8 +615,11 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen, "UTF8"))
return;
free((void *)tdmi->tdmi_network);
tdmi->tdmi_network = strdup(networkname);
if(strcmp(tdmi->tdmi_network ?: "", networkname)) {
free((void *)tdmi->tdmi_network);
tdmi->tdmi_network = strdup(networkname);
notify_tdmi_name_change(tdmi);
}
break;
}

12
http.c
View file

@ -253,6 +253,15 @@ http_xmit_queue(http_connection_t *hc)
}
/**
* Continue HTTP session, called by deferred replies
*/
void
http_continue(http_connection_t *hc)
{
if(http_xmit_queue(hc))
tcp_disconnect(&hc->hc_tcp_session, 0);
}
/**
* Send HTTP error back
@ -286,6 +295,7 @@ void
http_output(http_connection_t *hc, http_reply_t *hr, const char *content,
const char *encoding, int maxage)
{
hr->hr_destroy = NULL;
hr->hr_encoding = encoding;
hr->hr_content = content;
hr->hr_maxage = maxage;
@ -297,6 +307,7 @@ http_output(http_connection_t *hc, http_reply_t *hr, const char *content,
void
http_output_html(http_connection_t *hc, http_reply_t *hr)
{
hr->hr_destroy = NULL;
hr->hr_content = "text/html; charset=UTF-8";
}
@ -340,6 +351,7 @@ http_exec(http_connection_t *hc, http_path_t *hp, char *remain, int err)
TAILQ_INSERT_TAIL(&hc->hc_replies, hr, hr_link);
tcp_init_queue(&hr->hr_tq, -1);
hr->hr_connection = hc;
hr->hr_version = hc->hc_version;
hr->hr_keep_alive = hc->hc_keep_alive;

3
http.h
View file

@ -51,6 +51,7 @@ typedef struct http_reply {
void (*hr_destroy)(struct http_reply *hr, void *opaque);
struct http_connection *hr_connection;
int hr_version; /* HTTP version */
int hr_keep_alive;
@ -136,6 +137,8 @@ void http_output(http_connection_t *hc, http_reply_t *hr,
void http_output_html(http_connection_t *hc, http_reply_t *hr);
void http_continue(http_connection_t *hc);
void http_redirect(http_connection_t *hc, http_reply_t *hr,
const char *location);

47
notify.c Normal file
View file

@ -0,0 +1,47 @@
/*
* tvheadend, Notification framework
* Copyright (C) 2008 Andreas Öman
*
* 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 <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "tvhead.h"
#include "ajaxui/ajaxui_mailbox.h"
void
notify_tdmi_state_change(th_dvb_mux_instance_t *tdmi)
{
ajax_mailbox_tdmi_state_change(tdmi);
}
void
notify_tdmi_name_change(th_dvb_mux_instance_t *tdmi)
{
ajax_mailbox_tdmi_name_change(tdmi);
}
void
notify_tdmi_status_change(th_dvb_mux_instance_t *tdmi)
{
ajax_mailbox_tdmi_status_change(tdmi);
}

28
notify.h Normal file
View file

@ -0,0 +1,28 @@
/*
* tvheadend, Notification framework
* Copyright (C) 2008 Andreas Öman
*
* 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 NOTIFY_H_
#define NOTIFY_H_
void notify_tdmi_state_change(th_dvb_mux_instance_t *tdmi);
void notify_tdmi_name_change(th_dvb_mux_instance_t *tdmi);
void notify_tdmi_status_change(th_dvb_mux_instance_t *tdmi);
#endif /* NOTIFY_H_ */

View file

@ -167,6 +167,7 @@ typedef struct th_dvb_mux_instance {
dtimer_t tdmi_initial_scan_timer;
const char *tdmi_status;
const char *tdmi_last_status; /* For notification updates */
time_t tdmi_got_adapter;
time_t tdmi_lost_adapter;