tvheadend/htmlui.c

2311 lines
56 KiB
C

/*
* tvheadend, HTML 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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "tvhead.h"
#include "htmlui.h"
#include "channels.h"
#include "epg.h"
#include "pvr.h"
#include "strtab.h"
#include "dvb.h"
#include "v4l.h"
#include "iptv_input.h"
#include "transports.h"
#include "autorec.h"
#define MAIN_WIDTH 800
static struct strtab recstatustxt[] = {
{ "Recording scheduled",HTSTV_PVR_STATUS_SCHEDULED },
{ "Recording", HTSTV_PVR_STATUS_RECORDING },
{ "Done", HTSTV_PVR_STATUS_DONE },
{ "Recording aborted", HTSTV_PVR_STATUS_ABORTED },
{ "No transponder", HTSTV_PVR_STATUS_NO_TRANSPONDER },
{ "File error", HTSTV_PVR_STATUS_FILE_ERROR },
{ "Disk full", HTSTV_PVR_STATUS_DISK_FULL },
{ "Buffer error", HTSTV_PVR_STATUS_BUFFER_ERROR },
};
static struct strtab recintstatustxt[] = {
{ "Stopped", TFFM_STOP },
{ "Waiting for transponder", TFFM_WAIT_SUBSCRIPTION },
{ "Waiting for program start", TFFM_WAIT_FOR_START },
{ "Waiting for valid audio frames", TFFM_WAIT_AUDIO_LOCK },
{ "Waiting for valid video frames", TFFM_WAIT_VIDEO_LOCK },
{ "Recording", TFFM_RUNNING },
{ "Commercial break", TFFM_COMMERCIAL }
};
static struct strtab recstatuscolor[] = {
{ "#3333aa", HTSTV_PVR_STATUS_SCHEDULED },
{ "#aa3333", HTSTV_PVR_STATUS_RECORDING },
{ "#33aa33", HTSTV_PVR_STATUS_DONE },
{ "#aa3333", HTSTV_PVR_STATUS_ABORTED },
{ "#aa3333", HTSTV_PVR_STATUS_NO_TRANSPONDER },
{ "#aa3333", HTSTV_PVR_STATUS_FILE_ERROR },
{ "#aa3333", HTSTV_PVR_STATUS_DISK_FULL },
{ "#aa3333", HTSTV_PVR_STATUS_BUFFER_ERROR },
};
/**
* Compare start for two events, used as argument to qsort(3)
*/
static int
eventcmp(const void *A, const void *B)
{
const event_t *a = *(const event_t **)A;
const event_t *b = *(const event_t **)B;
return a->e_start - b->e_start;
}
/*
* Return 1 if current user have access to a feature
*/
static int
html_verify_access(http_connection_t *hc, const char *feature)
{
if(hc->hc_user_config == NULL)
return 0;
return atoi(config_get_str_sub(hc->hc_user_config, feature, "0"));
}
static int
pvrstatus_to_html(tv_pvr_status_t pvrstatus, const char **text,
const char **col)
{
*text = val2str(pvrstatus, recstatustxt);
if(*text == NULL)
return -1;
*col = val2str(pvrstatus, recstatuscolor);
if(*col == NULL)
return -1;
return 0;
}
/*
* Root page
*/
static int
page_css(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq,
"img { border: 0px; }\r\n"
"a:link { text-decoration: none}\r\n"
"a:visited { text-decoration: none}\r\n"
"a:active { text-decoration: none}\r\n"
"a:link { text-decoration: none; color: #000000}\r\n"
"a:visited { text-decoration: none; color: #000000}\r\n"
"a:active { text-decoration: none; color: #000000}\r\n"
"a:hover { text-decoration: underline; color: CC3333}\r\n"
""
"#box {background: #cccc99;}\r\n"
".roundtop {background: #ffffff;}\r\n"
".roundbottom {background: #ffffff;}\r\n"
".r1{margin: 0 5px; height: 1px; overflow: hidden; "
"background: #000000; border-left: 1px solid #000000; "
"border-right: 1px solid #000000;}\r\n"
""
".r2{margin: 0 3px; height: 1px; overflow: hidden; "
"background: #cccc99; border-left: 1px solid #000000; "
"border-right: 1px solid #000000; border-width: 0 2px;}\r\n"
""
".r3{margin: 0 2px; height: 1px; overflow: hidden; "
"background: #cccc99; border-left: 1px solid #000000; "
"border-right: 1px solid #000000;}\r\n"
""
".r4{margin: 0 1px; height: 2px; overflow: hidden; "
"background: #cccc99; border-left: 1px solid #000000; "
"border-right: 1px solid #000000;}\r\n"
""
".content3 {padding-left: 3px; height: 60px; "
"border-left: 1px solid #000000; "
"border-right: 1px solid #000000;}\r\n"
""
".content {padding-left: 3px; border-left: 1px solid #000000; "
"border-right: 1px solid #000000;}\r\n"
""
".contentbig {padding-left: 3px; "
"border-left: 1px solid #000000; "
"border-right: 1px solid #000000; "
"font: 150% Verdana, Arial, Helvetica, sans-serif;}\r\n"
""
".smalltxt {padding-left: 3px; "
"font: 100% Verdana, Arial, Helvetica, sans-serif;}\r\n"
""
".statuscont {float: left; margin: 4px; width: 400px}\r\n"
""
".logo {padding: 2px; width: 60px; height: 56px; "
"float: left};\r\n"
""
".over {float: left}\r\n"
".toptxt {float: left; width: 165px; text-align: center}\r\n"
""
".knapp {border: 1px dotted #000000; background: #ddddaa} "
".knapp:hover {border: 1px dotted #000000; background: #aaaa66}"
""
".drop {border: 1px dotted #000000; background: #ddddaa} "
""
".prioval {border: 0px; background: #ddddaa} "
""
"#meny {margin: 0; padding: 0}\r\n"
"#meny li{display: inline; list-style-type: none;}\r\n"
"#meny a{padding: 1.15em 0.8em; text-decoration: none;}\r\n"
);
http_output_queue(hc, &tq, "text/css", 60);
return 0;
}
static void
html_header(tcp_queue_t *tq, const char *title, int javascript, int width,
int autorefresh, const char *extrascript)
{
char w[30];
if(width > 0)
snprintf(w, sizeof(w), "width: %dpx; ", width);
else
w[0] = 0;
tcp_qprintf(tq,
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
"<html><head>\r\n"
"<title>%s</title>\r\n"
"<meta http-equiv=\"Content-Type\" "
"content=\"text/html; charset=utf-8\">\r\n", title);
if(autorefresh)
tcp_qprintf(tq,
"<meta http-equiv=\"refresh\" content=\"%d\">\r\n",
autorefresh);
tcp_qprintf(tq,
"<link href=\"/css.css\" rel=\"stylesheet\" type=\"text/css\">"
);
tcp_qprintf(tq,
"<style type=\"text/css\">\r\n"
"<!--\r\n"
"body {margin: 4px 4px; "
"font: 75% Verdana, Arial, Helvetica, sans-serif; "
"%s margin-right: auto; margin-left: auto;}\r\n"
"-->\r\n"
"</style>", w);
if(javascript) {
tcp_qprintf(tq,
"<script type=\"text/javascript\" "
"src=\"http://www.olebyn.nu/hts/overlib.js\"></script>\r\n");
}
if(extrascript)
tcp_qprintf(tq, "%s", extrascript);
/* BODY start */
tcp_qprintf(tq, "</head><body>\r\n");
if(javascript)
tcp_qprintf(tq,
"<div id=\"overDiv\" style=\"position:absolute; "
"visibility:hidden; z-index:1000;\"></div>\r\n");
}
static void
html_footer(tcp_queue_t *tq)
{
tcp_qprintf(tq,
"</body></html>\r\n\r\n");
}
static void
box_top(tcp_queue_t *tq, const char *style)
{
tcp_qprintf(tq, "<div id=\"%s\">"
"<div class=\"roundtop\">"
"<div class=\"r1\"></div>"
"<div class=\"r2\"></div>"
"<div class=\"r3\"></div>"
"<div class=\"r4\"></div>"
"</div>", style);
}
static void
box_bottom(tcp_queue_t *tq)
{
tcp_qprintf(tq,
"<div class=\"roundbottom\">"
"<div class=\"r4\"></div>"
"<div class=\"r3\"></div>"
"<div class=\"r2\"></div>"
"<div class=\"r1\"></div>"
"</div></div>");
}
static void
top_menu(http_connection_t *hc, tcp_queue_t *tq)
{
tcp_qprintf(tq, "<div style=\"width: %dpx; "
"margin-left: auto; margin-right: auto\">", MAIN_WIDTH);
box_top(tq, "box");
tcp_qprintf(tq,
"<div class=\"content\">"
"<ul id=\"meny\">");
tcp_qprintf(tq, "<li><a href=\"/\">Channel Guide</a></li>");
tcp_qprintf(tq, "<li><a href=\"/search\">Search</a></li>");
if(html_verify_access(hc, "record-events"))
tcp_qprintf(tq, "<li><a href=\"/pvr\">Recorder</a></li>");
if(html_verify_access(hc, "system-status"))
tcp_qprintf(tq, "<li><a href=\"/status\">System Status</a></li>");
if(html_verify_access(hc, "admin"))
tcp_qprintf(tq, "<li><a href=\"/chgrp\">Manage channel groups</a></li>");
if(html_verify_access(hc, "admin"))
tcp_qprintf(tq, "<li><a href=\"/chadm\">Manage channels</a></li>");
tcp_qprintf(tq, "</div>");
box_bottom(tq);
tcp_qprintf(tq, "</div><br>\r\n");
if(sys_warning != NULL) {
box_top(tq, "box");
tcp_qprintf(tq,
"<div class=\"content\">"
"<span style=\"color:#aa3333;font-weight:bold\">"
"Warning: %s</span></div>",
sys_warning);
box_bottom(tq);
tcp_qprintf(tq, "</div><br>\r\n");
}
}
void
esacpe_char(char *dst, int dstlen, const char *src, char c,
const char *repl)
{
char v;
const char *r;
while((v = *src++) && dstlen > 1) {
if(v != c) {
*dst++ = v;
dstlen--;
} else {
r = repl;
while(dstlen > 1 && *r) {
*dst++ = *r++;
dstlen--;
}
}
}
*dst = 0;
}
static int
is_client_simple(http_connection_t *hc)
{
char *c;
if((c = http_arg_get(&hc->hc_args, "UA-OS")) != NULL) {
if(strstr(c, "Windows CE") || strstr(c, "Pocket PC"))
return 1;
}
if((c = http_arg_get(&hc->hc_args, "x-wap-profile")) != NULL) {
return 1;
}
return 0;
}
/*
* Output_event
*/
static void
output_event(http_connection_t *hc, tcp_queue_t *tq, th_channel_t *ch,
event_t *e, int simple, int print_channel, int print_pvr,
int maxsize)
{
char title[100];
char bufa[4000];
char overlibstuff[4000];
struct tm a, b;
time_t stop;
event_t *cur;
tv_pvr_status_t pvrstatus;
const char *pvr_txt, *pvr_color;
pvr_rec_t *pvrr;
localtime_r(&e->e_start, &a);
stop = e->e_start + e->e_duration;
localtime_r(&stop, &b);
pvrr = pvr_get_by_entry(e);
pvrstatus = pvrr != NULL ? pvrr->pvrr_status : HTSTV_PVR_STATUS_NONE;
cur = epg_event_get_current(ch);
if(!simple && e->e_desc != NULL && e->e_desc[0] != 0) {
esacpe_char(bufa, sizeof(bufa), e->e_desc, '\'', "");
snprintf(overlibstuff, sizeof(overlibstuff),
"onmouseover=\"return overlib('%s')\" "
"onmouseout=\"return nd();\"",
bufa);
} else {
overlibstuff[0] = 0;
}
if(1 || simple) {
snprintf(bufa, sizeof(bufa),
"/event/%d", e->e_tag);
} else {
snprintf(bufa, sizeof(bufa),
"javascript:epop('/event/%d')", e->e_tag);
}
tcp_qprintf(tq, "<div><a href=\"%s\" %s>", bufa, overlibstuff);
if(print_channel) {
esacpe_char(title, sizeof(title), ch->ch_name, '"', "'");
tcp_qprintf(tq,
"<span style=\"overflow: hidden; height: 15px;"
"width: %dpx;float: left\">"
"%s</span>",
simple ? 80 : maxsize * 125 / 600, title);
}
esacpe_char(title, sizeof(title), e->e_title, '"', "'");
tcp_qprintf(tq,
"<span style=\"width: %dpx;height: 15px;overflow: hidden;"
"float: left%s\">"
"%02d:%02d - %02d:%02d</span>"
"<span style=\"overflow: hidden; height: 15px; width: %dpx; "
"float: left%s\">%s</span>",
simple ? 80 : maxsize * 125 / 600,
e == cur ? ";font-weight:bold" : "",
a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
simple ? 100 : maxsize * 350 / 600,
e == cur ? ";font-weight:bold" : "",
title
);
if(print_pvr && !pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color)) {
tcp_qprintf(tq,
"<span style=\"float:left;font-style:italic;"
"color:%s;font-weight:bold\">%s</span></a></div><br>",
pvr_color, pvr_txt);
} else {
tcp_qprintf(tq, "</a></div><br>");
}
}
/*
* Root page
*/
static int
page_root(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_t *ch;
event_t *e;
int i;
int simple = is_client_simple(hc);
time_t firstend = INT32_MAX;
th_channel_group_t *tcg;
struct sockaddr_in *si;
if(!html_verify_access(hc, "browse-events"))
return HTTP_STATUS_UNAUTHORIZED;
epg_lock();
tcp_init_queue(&tq, -1);
LIST_FOREACH(ch, &channels, ch_global_link) {
e = epg_event_find_current_or_upcoming(ch);
if(e && e->e_start + e->e_duration < firstend) {
firstend = e->e_start + e->e_duration;
}
}
i = 5 + firstend - dispatch_clock;
if(i < 30)
i = 30;
html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, i, NULL);
top_menu(hc, &tq);
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
tcg->tcg_name);
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link) {
if(LIST_FIRST(&ch->ch_transports) == NULL)
continue;
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content3\">");
if(!simple) {
tcp_qprintf(&tq, "<div class=\"logo\">");
if(ch->ch_icon) {
tcp_qprintf(&tq, "<a href=\"channel/%d\">"
"<img src=\"%s\" height=56px>"
"</a>",
ch->ch_tag,
refstr_get(ch->ch_icon));
}
tcp_qprintf(&tq, "</div>");
}
tcp_qprintf(&tq, "<div class=\"over\">");
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 300px; float: left; font-weight:bold\">"
"<a href=\"channel/%d\">%s</a></span>",
ch->ch_tag, ch->ch_name);
si = (struct sockaddr_in *)&hc->hc_tcp_session.tcp_self_addr;
tcp_qprintf(&tq,
"<i><a href=\"rtsp://%s:%d/%s\">Watch live</a></i><br>",
inet_ntoa(si->sin_addr), ntohs(si->sin_port),
ch->ch_sname);
e = epg_event_find_current_or_upcoming(ch);
for(i = 0; i < 3 && e != NULL; i++) {
output_event(hc, &tq, ch, e, simple, 0, 1, 580);
e = TAILQ_NEXT(e, e_link);
}
tcp_qprintf(&tq, "</div></div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>\r\n");
}
}
epg_unlock();
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/*
* Channel page
*/
const char *days[7] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
};
static int
page_channel(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_t *ch = NULL;
event_t *e = NULL;
int i;
int simple = is_client_simple(hc);
int channeltag = -1;
int w, doff = 0, wday;
struct tm a;
if(!html_verify_access(hc, "browse-events"))
return HTTP_STATUS_UNAUTHORIZED;
i = sscanf(remain, "%d/%d", &channeltag, &doff);
ch = channel_by_tag(channeltag);
if(i != 2)
doff = 0;
if(ch == NULL) {
http_error(hc, 404);
return 0;
}
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
epg_lock();
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq, "<strong><a href=\"channel/%d\">%s</a></strong><br>",
ch->ch_tag, ch->ch_name);
e = epg_event_find_current_or_upcoming(ch);
if(e != NULL) {
localtime_r(&e->e_start, &a);
wday = a.tm_wday;
for(w = 0; w < 7; w++) {
tcp_qprintf(&tq,
"<a href=\"/channel/%d/%d\">"
"<i><u>%s</i></u></a><br>",
ch->ch_tag, w,
days[(wday + w) % 7]);
while(e != NULL) {
localtime_r(&e->e_start, &a);
if(a.tm_wday != wday + w)
break;
if(a.tm_wday == wday + doff) {
output_event(hc, &tq, ch, e, simple, 0, 1, 580);
}
e = TAILQ_NEXT(e, e_link);
}
}
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>\r\n");
epg_unlock();
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Event page
*/
static int
page_event(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
int simple = is_client_simple(hc);
int eventid = atoi(remain);
char title[100];
event_t *e;
struct tm a, b;
time_t stop;
char desc[4000];
tv_pvr_status_t pvrstatus;
pvr_rec_t *pvrr;
const char *pvr_txt, *pvr_color;
if(!html_verify_access(hc, "browse-events"))
return HTTP_STATUS_UNAUTHORIZED;
epg_lock();
e = epg_event_find_by_tag(eventid);
if(e == NULL) {
epg_unlock();
return 404;
}
pvrr = pvr_get_by_entry(e);
if(http_arg_get(&hc->hc_req_args, "rec")) {
if(!html_verify_access(hc, "record-events")) {
epg_unlock();
return HTTP_STATUS_UNAUTHORIZED;
}
pvrr = pvr_schedule_by_event(e, hc->hc_username);
}
if(pvrr != NULL && http_arg_get(&hc->hc_req_args, "cancel")) {
if(!html_verify_access(hc, "record-events")) {
epg_unlock();
return HTTP_STATUS_UNAUTHORIZED;
}
pvr_abort(pvrr);
}
if(pvrr != NULL && http_arg_get(&hc->hc_req_args, "clear")) {
if(!html_verify_access(hc, "record-events")) {
epg_unlock();
return HTTP_STATUS_UNAUTHORIZED;
}
pvr_clear(pvrr);
pvrr = NULL;
}
pvrstatus = pvrr != NULL ? pvrr->pvrr_status : HTSTV_PVR_STATUS_NONE;
localtime_r(&e->e_start, &a);
stop = e->e_start + e->e_duration;
localtime_r(&stop, &b);
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
tcp_qprintf(&tq, "<form method=\"get\" action=\"/event/%d\">", eventid);
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
esacpe_char(title, sizeof(title), e->e_title, '"', "'");
esacpe_char(desc, sizeof(desc), e->e_desc, '\'', "");
tcp_qprintf(&tq,
"<div style=\"width: %dpx;float: left;font-weight:bold\">"
"%02d:%02d - %02d:%02d</div>"
"<div style=\"width: %dpx; float: left;font-weight:bold\">"
"%s</div></a>",
simple ? 80 : 100,
a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
simple ? 100 : 250,
title);
if(!pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color))
tcp_qprintf(&tq,
"<div style=\"font-style:italic;color:%s;font-weight:bold\">"
"%s</div>",
pvr_color, pvr_txt);
else
tcp_qprintf(&tq, "<br>");
tcp_qprintf(&tq, "<br>%s", desc);
tcp_qprintf(&tq,"<div style=\"text-align: center\">");
if(html_verify_access(hc, "record-events")) {
switch(pvrstatus) {
case HTSTV_PVR_STATUS_SCHEDULED:
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"clear\" "
"value=\"Remove schedule\">");
break;
case HTSTV_PVR_STATUS_RECORDING:
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"cancel\" "
"value=\"Cancel recording\">");
break;
case HTSTV_PVR_STATUS_NONE:
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"rec\" "
"value=\"Record\">");
break;
default:
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"clear\" "
"value=\"Clear error status\">");
break;
}
}
tcp_qprintf(&tq, "</div></div></div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</form>\r\n");
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
epg_unlock();
return 0;
}
static int
pvrcmp(const void *A, const void *B)
{
const pvr_rec_t *a = *(const pvr_rec_t **)A;
const pvr_rec_t *b = *(const pvr_rec_t **)B;
return a->pvrr_start - b->pvrr_start;
}
/**
* PVR main page
*/
static int
page_pvr(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
int simple = is_client_simple(hc);
pvr_rec_t *pvrr, *pvrr_tgt;
char escapebuf[4000];
char title[100];
char channel[100];
struct tm a, b, day;
const char *pvr_txt, *pvr_color, *buttontxt, *cmd, *txt;
char buttonname[100];
int c, i;
pvr_rec_t **pv;
int divid = 1;
int size = 600;
http_arg_t *ra;
autorec_t *ar, *ar_show = NULL;
if(!html_verify_access(hc, "record-events"))
return HTTP_STATUS_UNAUTHORIZED;
if(http_arg_get(&hc->hc_req_args, "clearall")) {
pvr_clear_all_completed();
}
pvrr_tgt = NULL;
TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
c = 0;
if(!strncmp(ra->key, "ardel_", 6)) {
txt = ra->key + 6;
autorec_delete_by_id(atoi(txt));
/* Redirect back to ourself, to get rid of URL cruft */
http_redirect(hc, "/pvr");
return 0;
} else if(!strncmp(ra->key, "clear_", 6)) {
txt = ra->key + 6;
} else if(!strncmp(ra->key, "desched_", 8)) {
txt = ra->key + 8;
} else if(!strncmp(ra->key, "abort_", 6)) {
c = 1;
txt = ra->key + 6;
} else {
continue;
}
pvrr_tgt = pvr_get_tag_entry(atoi(txt));
if(pvrr_tgt != NULL) {
if(c)
pvr_abort(pvrr_tgt);
else
pvr_clear(pvrr_tgt);
}
break;
}
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0,
"<script type=\"text/javascript\">\n"
"function expcol(id)\n"
"{\n"
" var div = document.getElementById(id);\n"
" if (div.style.display != \"none\") {\n"
" div.style.display = \"none\";\n"
" } else {\n"
" div.style.display = \"block\";\n"
" }\n"
"}\n"
"</script>\n");
top_menu(hc, &tq);
tcp_qprintf(&tq, "<form method=\"get\" action=\"/pvr\">");
/*
* PVR log
*/
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
"Recorder Log");
tcp_qprintf(&tq, "<div class=\"content\">");
c = 0;
LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link)
c++;
if(c == 0) {
tcp_qprintf(&tq, "<center>No entries</center><br>");
}
pv = alloca(c * sizeof(pvr_rec_t *));
i = 0;
LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link)
pv[i++] = pvrr;
qsort(pv, i, sizeof(pvr_rec_t *), pvrcmp);
memset(&day, -1, sizeof(struct tm));
for(i = 0; i < c; i++) {
pvrr = pv[i];
localtime_r(&pvrr->pvrr_start, &a);
localtime_r(&pvrr->pvrr_stop, &b);
if(a.tm_wday != day.tm_wday || a.tm_mday != day.tm_mday ||
a.tm_mon != day.tm_mon || a.tm_year != day.tm_year) {
memcpy(&day, &a, sizeof(struct tm));
tcp_qprintf(&tq, "<br><b><i>%s, %d/%d</i></b><br>",
days[day.tm_wday], day.tm_mday, day.tm_mon + 1);
}
tcp_qprintf(&tq,
"<div><a href=\"javascript:expcol('div%d')\"",
divid);
if(!simple && pvrr->pvrr_desc != NULL && pvrr->pvrr_desc[0] != 0) {
esacpe_char(escapebuf, sizeof(escapebuf), pvrr->pvrr_desc, '\'', "");
tcp_qprintf(&tq,
" onmouseover=\"return overlib('%s')\""
" onmouseout=\"return nd();\"",
escapebuf);
}
tcp_qprintf(&tq, ">");
esacpe_char(channel, sizeof(channel),
pvrr->pvrr_channel->ch_name, '"', "'");
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px;"
"width: %dpx;float: left\">"
"%s</span>",
size * 125 / 600,
channel);
esacpe_char(title, sizeof(title), pvrr->pvrr_title, '"', "'");
tcp_qprintf(&tq,
"<span style=\"width: %dpx;height: 15px;overflow: hidden;"
"float: left\">"
"%02d:%02d - %02d:%02d</span>"
"<span style=\"overflow: hidden; height: 15px; width: %dpx; "
"float: left\">%s</span>",
size * 125 / 600,
a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
size * 350 / 600,
title);
if(!pvrstatus_to_html(pvrr->pvrr_status, &pvr_txt, &pvr_color)) {
tcp_qprintf(&tq,
"<span style=\"float:left;font-style:italic;"
"color:%s;font-weight:bold\">%s</span>",
pvr_color, pvr_txt);
}
tcp_qprintf(&tq, "</a></div><br>");
tcp_qprintf(&tq,
"<div id=\"div%d\" style=\"display:%s; "
"border-bottom-width:thin; border-bottom-style:solid\">",
divid, pvrr_tgt == pvrr ? "block" : "none");
tcp_qprintf(&tq,
"<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" "
"style=\"font: 85% Verdana, Arial, Helvetica, sans-serif\">");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Created by:</span><td>"
"<td>%s</td>"
"</tr>",
pvrr->pvrr_creator ?: "<i>not set</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Filename:</span><td>"
"<td>%s</td>"
"</tr>",
pvrr->pvrr_filename ?: "<i>not set</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Recorder status:</span><td>"
"<td>%s</td>"
"</tr>",
val2str(pvrr->pvrr_tffm.tffm_state, recintstatustxt)
?: "invalid");
tcp_qprintf(&tq,
"</table>");
switch(pvrr->pvrr_status) {
case HTSTV_PVR_STATUS_SCHEDULED:
buttontxt = "Remove from schedule";
cmd = "desched";
break;
case HTSTV_PVR_STATUS_RECORDING:
buttontxt = "Abort recording";
cmd = "abort";
break;
default:
buttontxt = "Remove log entry";
cmd = "clear";
break;
}
snprintf(buttonname, sizeof(buttonname), "%s_%d", cmd, pvrr->pvrr_ref);
tcp_qprintf(&tq,"<div style=\"text-align: center\">"
"<input type=\"submit\" class=\"knapp\" name=\"%s\" "
"value=\"%s\"></div>", buttonname, buttontxt);
tcp_qprintf(&tq, "<br></div>\n");
divid++;
}
tcp_qprintf(&tq,
"<br><div style=\"text-align: center\">"
"<input type=\"submit\" class=\"knapp\" name=\"clearall\" "
"value=\"Remove all completed recording log entries\"></div>");
tcp_qprintf(&tq, "</div>\r\n");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>\r\n");
/*
* Autorecorder
*/
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
"Autorecorder ruleset");
tcp_qprintf(&tq, "<div class=\"content\">\r\n");
if(TAILQ_FIRST(&autorecs) == NULL)
tcp_qprintf(&tq, "<center>No entries</center><br>");
TAILQ_FOREACH(ar, &autorecs, ar_link) {
tcp_qprintf(&tq,
"<div><a href=\"javascript:expcol('div%d')\">"
"%s</a></div><br>\r\n",
divid, ar->ar_name);
tcp_qprintf(&tq,
"<div id=\"div%d\" style=\"display:%s; "
"border-bottom-width:thin; border-bottom-style:solid\">",
divid, ar_show == ar ? "block" : "none");
tcp_qprintf(&tq,
"<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" "
"style=\"font: 85% Verdana, Arial, Helvetica, sans-serif\">");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Created by:</span><td>"
"<td>%s</td>"
"</tr>",
ar->ar_creator ?: "<i>not set</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Recording priority:</span><td>"
"<td>%d</td>"
"</tr>",
ar->ar_rec_prio);
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Event tile:</span><td>"
"<td>%s</td>"
"</tr>",
ar->ar_title ?: "<i>All</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Content group:</span><td>"
"<td>%s</td>"
"</tr>",
ar->ar_ecg ? ar->ar_ecg->ecg_name: "<i>All</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Channel group:</span><td>"
"<td>%s</td>"
"</tr>",
ar->ar_tcg ? ar->ar_tcg->tcg_name: "<i>All</i>");
tcp_qprintf(&tq,
"<tr>"
"<td width=125><span style=\"text-align: right\">"
"Channel:</span><td>"
"<td>%s</td>"
"</tr>",
ar->ar_ch ? ar->ar_ch->ch_name: "<i>All</i>");
tcp_qprintf(&tq,
"</table>");
tcp_qprintf(&tq,"<div style=\"text-align: center\">"
"<input type=\"submit\" class=\"knapp\" name=\"ardel_%d\" "
"value=\"Delete rule\"></div>", ar->ar_id);
tcp_qprintf(&tq, "<br></div>\n");
divid++;
}
tcp_qprintf(&tq, "<br></div>\r\n");
box_bottom(&tq);
tcp_qprintf(&tq, "</form>\r\n");
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
static void
html_iptv_status(tcp_queue_t *tq, th_transport_t *t, const char *status)
{
tcp_qprintf(tq,
"<span style=\"overflow: hidden; height: 15px; width: 270px; "
"float:left; font-weight:bold\">"
"%s (%s)"
"</span>",
inet_ntoa(t->tht_iptv_group_addr),
t->tht_channel->ch_name);
tcp_qprintf(tq,
"<span style=\"overflow: hidden; height: 15px; width: 100px; "
"float:left\">"
"%s"
"</span><br>",
status);
}
/**
* System status
*/
static int
page_status(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
int simple = is_client_simple(hc);
th_dvb_adapter_t *tda;
th_v4l_adapter_t *tva;
th_subscription_t *s;
th_transport_t *t;
th_dvb_mux_instance_t *tdmi;
th_stream_t *st;
const char *txt, *t1, *t2;
char tmptxt[100];
int i, v, vv;
if(!html_verify_access(hc, "system-status"))
return HTTP_STATUS_UNAUTHORIZED;
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, -1, 0, NULL);
top_menu(hc, &tq);
tcp_qprintf(&tq, "<div style=\"width: 1300px; margin: auto\">");
tcp_qprintf(&tq, "<div class=\"statuscont\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"contentbig\">");
tcp_qprintf(&tq, "<b><center>Input devices</b><br>");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
/* DVB adapters */
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq, "<b><center>DVB adapters</center></b>");
if(LIST_FIRST(&dvb_adapters_running) == NULL) {
tcp_qprintf(&tq, "No DVB adapters configured<br>");
} else {
LIST_FOREACH(tda, &dvb_adapters_running, tda_link) {
tcp_qprintf(&tq, "<br><b>%s</b><br>%s<br>",
tda->tda_rootpath, tda->tda_info);
LIST_FOREACH(tdmi, &tda->tda_muxes_active, tdmi_adapter_link) {
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 160px; float: left\">"
"%s"
"</span>",
tdmi->tdmi_shortname);
txt = tdmi->tdmi_status ?: "Ok";
v = vv = 0;
for(i = 0; i < TDMI_FEC_ERR_HISTOGRAM_SIZE; i++) {
if(tdmi->tdmi_fec_err_histogram[i] > DVB_FEC_ERROR_LIMIT)
v++;
vv += tdmi->tdmi_fec_err_histogram[i];
}
vv /= TDMI_FEC_ERR_HISTOGRAM_SIZE;
if(v == TDMI_FEC_ERR_HISTOGRAM_SIZE)
txt = "Constant high FEC rate";
else if(v > 0)
txt = "Bursty FEC rate";
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 120px; float: left\">"
"%s"
"</span>",
txt);
switch(tdmi->tdmi_state) {
default:
txt = "???";
break;
case TDMI_IDLE:
txt = "Idle";
break;
case TDMI_RUNNING:
txt = "Running";
break;
case TDMI_IDLESCAN:
txt = "IdleScan";
break;
}
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 60px; float: left\">"
"%s"
"</span>",
txt);
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 50px; float: left\">"
"%d"
"</span><br>",
vv);
}
}
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
/* IPTV adapters */
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq, "<b><center>IPTV sources</center></b>");
LIST_FOREACH(t, &all_transports, tht_global_link) {
if(t->tht_type != TRANSPORT_IPTV)
continue;
html_iptv_status(&tq, t,
t->tht_status == TRANSPORT_IDLE ? "Idle" : "Running");
}
LIST_FOREACH(t, &iptv_stale_transports, tht_adapter_link)
html_iptv_status(&tq, t, "Probe failed");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
/* Video4Linux adapters */
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq, "<b><center>Video4Linux adapters</center></b>");
LIST_FOREACH(tva, &v4l_adapters, tva_link) {
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; width: 270px; "
"float:left; font-weight:bold\">"
"%s"
"</span>",
tva->tva_path);
if(tva->tva_dispatch_handle == NULL) {
snprintf(tmptxt, sizeof(tmptxt), "Idle");
} else {
snprintf(tmptxt, sizeof(tmptxt), "Tuned to %.3f MHz",
(float)tva->tva_frequency/1000000.0);
}
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; width: 100px; "
"float:left\">"
"%s"
"</span><br>",
tmptxt);
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</div>");
/* Active transports */
tcp_qprintf(&tq, "<div class=\"statuscont\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"contentbig\">");
tcp_qprintf(&tq, "<b><center>Active transports</b><br>");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
LIST_FOREACH(t, &all_transports, tht_global_link) {
if(t->tht_status != TRANSPORT_RUNNING)
continue;
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 200px; float: left; font-weight:bold\">"
"%s"
"</span>"
"<span style=\"overflow: hidden; height: 15px; "
"width: 190px; float: left\">"
"%s"
"</span><br>",
t->tht_name,
t->tht_channel->ch_name);
switch(t->tht_type) {
case TRANSPORT_IPTV:
t1 = tmptxt;
snprintf(tmptxt, sizeof(tmptxt), "IPTV: %s",
inet_ntoa(t->tht_iptv_group_addr));
t2 = "";
break;
case TRANSPORT_V4L:
t1 = tmptxt;
snprintf(tmptxt, sizeof(tmptxt), "V4L: %.3f MHz",
(float)t->tht_v4l_frequency / 1000000.0f);
t2 = t->tht_v4l_adapter->tva_path;
break;
case TRANSPORT_DVB:
t1 = t->tht_dvb_mux_instance->tdmi_shortname;
t2 = t->tht_dvb_mux_instance->tdmi_adapter->tda_rootpath;
break;
case TRANSPORT_AVGEN:
t1 = "A/V Generator";
t2 = "";
break;
default:
continue;
}
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 200px; float: left;\">"
"%s"
"</span>"
"<span style=\"overflow: hidden; height: 15px; "
"width: 190px; float: left\">"
"%s"
"</span><br><br>",
t1, t2);
LIST_FOREACH(st, &t->tht_streams, st_link) {
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 100px; float: left\">"
"%s"
"</span>",
htstvstreamtype2txt(st->st_type));
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 100px; float: left\">"
"%d kb/s"
"</span>",
avgstat_read_and_expire(&st->st_rate, dispatch_clock)
/ 1000);
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 100px; float: left\">"
"%d errors/s"
"</span><br>",
avgstat_read_and_expire(&st->st_cc_errors, dispatch_clock));
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
}
tcp_qprintf(&tq, "</div>");
/* Subscribers */
tcp_qprintf(&tq, "<div class=\"statuscont\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"contentbig\">");
tcp_qprintf(&tq, "<b><center>Subscriptions</b><br>");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
LIST_FOREACH(s, &subscriptions, ths_global_link) {
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">");
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 230px; float: left; font-weight:bold\">"
"%s"
"</span>",
s->ths_title);
tcp_qprintf(&tq,
"<span style=\"overflow: hidden; height: 15px; "
"width: 160px; float: left\">"
"%s"
"</span><br>",
s->ths_channel->ch_name);
if((t = s->ths_transport) == NULL) {
tcp_qprintf(&tq,
"<i>No transport available</i><br>");
} else {
tcp_qprintf(&tq,
"Using transport \"%s\"<br>",
t->tht_name);
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
}
tcp_qprintf(&tq, "</div>");
tcp_qprintf(&tq, "</div>");
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Manage channel groups
*/
static int
page_chgroups(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_group_t *tcg;
th_channel_t *ch;
int cnt;
const char *grp;
http_arg_t *ra;
if(!html_verify_access(hc, "admin"))
return HTTP_STATUS_UNAUTHORIZED;
if((grp = http_arg_get(&hc->hc_req_args, "newgrpname")) != NULL)
channel_group_find(grp, 1);
TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
if(!strncmp(ra->key, "delgroup", 8)) {
tcg = channel_group_by_tag(atoi(ra->key + 8));
if(tcg != NULL)
channel_group_destroy(tcg);
break;
}
if(!strncmp(ra->key, "up", 2)) {
tcg = channel_group_by_tag(atoi(ra->key + 2));
if(tcg != NULL)
channel_group_move_prev(tcg);
break;
}
if(!strncmp(ra->key, "down", 4)) {
tcg = channel_group_by_tag(atoi(ra->key + 4));
if(tcg != NULL)
channel_group_move_next(tcg);
break;
}
}
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
tcp_qprintf(&tq, "<form method=\"get\" action=\"/chgrp\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content3\">");
cnt = 0;
TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link)
cnt++;
tcp_qprintf(&tq, "<b>%s</b> (%d channels)<br><br>", tcg->tcg_name, cnt);
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"up%d\""
" value=\"Move up\"> ", tcg->tcg_tag);
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"down%d\""
" value=\"Move down\"> ", tcg->tcg_tag);
if(tcg->tcg_cant_delete_me == 0) {
tcp_qprintf(&tq,
"<input type=\"submit\" class=\"knapp\" name=\"delgroup%d\""
" value=\"Delete this group\">", tcg->tcg_tag);
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</form>\r\n");
}
tcp_qprintf(&tq, "<form method=\"get\" action=\"/chgrp\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\">"
"<input type=\"text\" name=\"newgrpname\""
" style=\"border: 1px dotted #000000\"> "
"<input type=\"submit\" class=\"knapp\" name=\"newgrp\""
" value=\"Add new group\">"
"</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</form><br>\r\n");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Manage channels
*/
static int
page_chadm(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
int simple = is_client_simple(hc);
if(!html_verify_access(hc, "admin"))
return HTTP_STATUS_UNAUTHORIZED;
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
tcp_qprintf(&tq,
"<table border=\"0\" cellspacing=\"0\" cellpadding=\"5\"> "
"<tr><td>");
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"content\" style=\"width: 260px\">"
"<iframe src=\"/chadm2\" frameborder=\"0\" "
"width=\"250\" height=\"600\"></iframe>"
"</div>");
box_bottom(&tq);
tcp_qprintf(&tq,
"</td><td>"
"<iframe name=\"chf\" frameborder=\"0\" "
"width=\"530\" height=\"600\" scrolling=\"no\"></iframe>"
"</td></tr></table>");
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
static int
page_chadm2(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_t *ch;
int simple = is_client_simple(hc);
if(!html_verify_access(hc, "admin"))
return HTTP_STATUS_UNAUTHORIZED;
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, 230, 0, NULL);
LIST_FOREACH(ch, &channels, ch_global_link) {
tcp_qprintf(&tq,
"<a href=\"/editchannel/%d\" target=\"chf\">%s</a><br>",
ch->ch_tag, ch->ch_name);
}
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Edit a single channel
*/
static int
page_editchannel(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_t *ch, *ch2;
th_channel_group_t *tcg, *dis;
th_transport_t *t;
if(!html_verify_access(hc, "admin"))
return HTTP_STATUS_UNAUTHORIZED;
if(remain == NULL || (ch = channel_by_tag(atoi(remain))) == NULL)
return 404;
dis = channel_group_find("-disabled-", 1);
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", 0, 530, 0, NULL);
tcp_qprintf(&tq,
"<form method=\"get\" action=\"/updatechannel/%d\">",
ch->ch_tag);
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
ch->ch_name);
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"content\">"
"<div style=\"width: 170px; float: left\">"
"Channel group: "
"</div>"
"<span style=\"width: 250px\">"
"<select name=\"grp\" class=\"drop\" "
"onChange=\"this.form.submit()\">");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
tcp_qprintf(&tq, "<option%s>%s</option>",
ch->ch_group == tcg ? " selected" : "",
tcg->tcg_name);
}
tcp_qprintf(&tq, "<option%s>%s</option></select></span>",
ch->ch_group == dis ? " selected" : "",
dis->tcg_name);
tcp_qprintf(&tq, "<br><br>\r\n");
tcp_qprintf(&tq,
"<div style=\"width: 170px; float: left\">"
"Merge with channel: "
"</div>"
"<span style=\"width: 250px\">"
"<select name=\"merge\" class=\"drop\" "
"onChange=\"this.form.submit()\">");
tcp_qprintf(&tq, "<option selected>-select-</option>");
LIST_FOREACH(ch2, &channels, ch_global_link) {
if(ch2 == ch)
continue;
tcp_qprintf(&tq, "<option>%s</option>", ch2->ch_name);
}
tcp_qprintf(&tq, "</select></span><br><br>\r\n");
tcp_qprintf(&tq,
"<div style=\"width: 170px; float: left\">"
"Teletext rundown page: "
"</div>"
"<span style=\"width: 250px\">"
"<input class=\"drop\" type=\"text\" name=\"ttrp\" "
"maxlength=\"3\" value=\"%d\" size=\"4\" "
"onChange=\"this.form.submit()\"></span><br>",
ch->ch_teletext_rundown);
tcp_qprintf(&tq, "<br><br>\r\n");
tcp_qprintf(&tq,
"<span style=\"font-weight:bold;\">"
"<center>Transports</center></span><br>"
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\" "
"style=\"font: 75% Verdana, Arial, Helvetica, sans-serif\">");
tcp_qprintf(&tq,
"<tr>"
"<th width=\"45\">Priority</td>"
"<th width=\"60\">Scrambled</td>"
"<th width=\"130\">Provider</td>"
"<th width=\"130\">Network</td>"
"<th width=\"140\">Transport ID</td>"
"</tr>");
LIST_FOREACH(t, &ch->ch_transports, tht_channel_link) {
tcp_qprintf(&tq,
"<tr>"
"<td><center>"
"<input class=\"prioval\" type=\"text\" name=\"%s\" "
"maxlength=\"4\" value=\"%d\" size=\"5\" "
"onChange=\"this.form.submit()\">"
"</center></td>"
"<td><center>%s</center></td>"
"<td><center>%s</center></td>"
"<td><center>%s</center></td>"
"<td><center>%s</center></td>"
"</tr>",
t->tht_uniquename,
t->tht_prio,
t->tht_scrambled ? "Yes" : "No",
t->tht_provider,
t->tht_network,
t->tht_name);
}
tcp_qprintf(&tq, "</table>\r\n");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</form>");
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Update a single channel, then redirect back to the edit page
*/
static int
page_updatechannel(http_connection_t *hc, const char *remain, void *opaque)
{
th_channel_t *ch, *ch2;
th_transport_t *t, **tv;
th_channel_group_t *tcg;
const char *grp, *s;
char buf[100];
int pri, i, n;
if(!html_verify_access(hc, "admin"))
return HTTP_STATUS_UNAUTHORIZED;
if(remain == NULL || (ch = channel_by_tag(atoi(remain))) == NULL)
return 404;
if((s = http_arg_get(&hc->hc_req_args, "merge")) != NULL) {
ch2 = channel_find(s, 0, NULL);
if(ch2 != NULL) {
if(LIST_FIRST(&ch->ch_subscriptions) == NULL) {
while((t = LIST_FIRST(&ch->ch_transports)) != NULL) {
transport_move(t, ch2);
}
}
/* Redirect to new channel */
snprintf(buf, sizeof(buf), "/editchannel/%d", ch2->ch_tag);
http_redirect(hc, buf);
return 0;
}
}
if((s = http_arg_get(&hc->hc_req_args, "ttrp")) != NULL)
channel_set_teletext_rundown(ch, atoi(s));
if((grp = http_arg_get(&hc->hc_req_args, "grp")) != NULL) {
tcg = channel_group_find(grp, 1);
channel_set_group(ch, tcg);
}
/* We are going to rearrange listorder by changing priority, so we
cannot just loop the list */
n = 0;
LIST_FOREACH(t, &ch->ch_transports, tht_channel_link)
n++;
tv = alloca(n * sizeof(th_transport_t *));
n = 0;
LIST_FOREACH(t, &ch->ch_transports, tht_channel_link)
tv[n++] = t;
for(i = 0; i < n; i++) {
t = tv[i];
s = http_arg_get(&hc->hc_req_args, t->tht_uniquename);
if(s != NULL) {
pri = atoi(s);
if(pri >= 0 && pri <= 9999) {
transport_set_priority(t, pri);
}
}
}
snprintf(buf, sizeof(buf), "/editchannel/%d", ch->ch_tag);
http_redirect(hc, buf);
return 0;
}
/**
* Search for a specific event
*/
static int
page_search(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
epg_content_group_t *ecg, *s_ecg;
th_channel_group_t *tcg, *s_tcg;
th_channel_t *ch, *s_ch;
int simple = is_client_simple(hc);
int i, c, k;
char escapebuf[1000];
struct event_list events;
event_t *e, **ev;
struct tm a, day;
const char *search = http_arg_get(&hc->hc_req_args, "s");
const char *autorec = http_arg_get(&hc->hc_req_args, "ar");
const char *title = http_arg_get(&hc->hc_req_args, "n");
const char *content = http_arg_get(&hc->hc_req_args, "c");
const char *chgroup = http_arg_get(&hc->hc_req_args, "g");
const char *channel = http_arg_get(&hc->hc_req_args, "ch");
const char *ar_name = http_arg_get(&hc->hc_req_args, "ar_name");
const char *ar_prio = http_arg_get(&hc->hc_req_args, "ar_prio");
if(title != NULL && *title == 0) title = NULL;
if(content != NULL && !strcmp(content, "-All-")) content = NULL;
if(chgroup != NULL && !strcmp(chgroup, "-All-")) chgroup = NULL;
if(channel != NULL && !strcmp(channel, "-All-")) channel = NULL;
if(ar_name != NULL && *ar_name == 0) ar_name = NULL;
if(ar_prio != NULL && *ar_prio == 0) ar_prio = NULL;
s_ecg = content ? epg_content_group_find_by_name(content) : NULL;
s_tcg = chgroup ? channel_group_find(chgroup, 0) : NULL;
s_ch = channel ? channel_find(channel, 0, NULL) : NULL;
if(http_arg_get(&hc->hc_req_args, "ar_create")) {
/* Create autorecording */
if(ar_name == NULL || ar_prio == NULL || atoi(ar_prio) < 1) {
/* Invalid arguments */
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
tcp_qprintf(&tq, "<form method=\"get\" action=\"/search\">");
box_top(&tq, "box");
tcp_qprintf(&tq, "<div class=\"content\"><br>"
"<b>Invalid / Missing arguments:<br><br>");
if(ar_name == NULL)
tcp_qprintf(&tq,
"- Name is missing<br><br>");
if(ar_prio == NULL || atoi(ar_prio) < 1)
tcp_qprintf(&tq,
"- Priority is missing or not a positive number<br><br>");
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/* Create autorec rule .. */
autorec_create(ar_name, atoi(ar_prio), title, s_ecg, s_tcg, s_ch,
hc->hc_username);
/* .. and redirect user to video recorder page */
http_redirect(hc, "/pvr");
return 0;
}
tcp_init_queue(&tq, -1);
html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL);
top_menu(hc, &tq);
tcp_qprintf(&tq,
"<form method=\"get\" action=\"/search\">");
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"content\">"
"<br>");
/* Title */
tcp_qprintf(&tq,
"<div>"
"<span style=\"text-align: right; width: 120px; float: left\">"
"Event title:"
"</span>"
"<span style=\"width: 275px; float: left\">"
"<input type=\"text\" size=40 name=\"n\"");
if(title != NULL) {
esacpe_char(escapebuf, sizeof(escapebuf), title, '"', "&quot;");
tcp_qprintf(&tq, " value=\"%s\"", escapebuf);
}
tcp_qprintf(&tq,
" style=\"border: 1px dotted #000000\"> "
"</span>");
/* Specific content */
tcp_qprintf(&tq,
"<span style=\"text-align: right; width: 120px; float: left\">"
"Content:"
"</span>"
"<span style=\"width: 275px\">"
"<select name=\"c\" class=\"drop\">");
tcp_qprintf(&tq, "<option%s>-All-</option>", content ? "" : " selected");
for(i = 0; i < 16; i++) {
ecg = epg_content_groups[i];
if(ecg->ecg_name == NULL)
continue;
tcp_qprintf(&tq, "<option%s>%s</option>",
ecg == s_ecg ? " selected" : "", ecg->ecg_name);
}
tcp_qprintf(&tq, "</select></span></div><br>");
/* Specific channel group */
tcp_qprintf(&tq,
"<div>"
"<span style=\"text-align: right; width: 120px; float: left\">"
"Channel group:"
"</span>"
"<span style=\"width: 275px; float: left\">"
"<select name=\"g\" class=\"drop\">");
tcp_qprintf(&tq, "<option%s>-All-</option>", chgroup ? "" : " selected");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
tcp_qprintf(&tq, "<option%s>%s</option>",
tcg == s_tcg ? " selected" : "", tcg->tcg_name);
}
tcp_qprintf(&tq, "</select></span>");
/* Specific channel */
tcp_qprintf(&tq,
"<span style=\"text-align: right; width: 120px; float: left\">"
"Channel:"
"</span>"
"<span style=\"width: 275px; float: left\">"
"<select name=\"ch\" class=\"drop\">");
tcp_qprintf(&tq, "<option%s>-All-</option>", channel ? "" : " selected");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link) {
if(LIST_FIRST(&ch->ch_transports) == NULL)
continue;
tcp_qprintf(&tq, "<option%s>%s</option>",
ch == s_ch ? " selected" : "", ch->ch_name);
}
}
tcp_qprintf(&tq, "</select></span></div><br><br>");
if(autorec != NULL) {
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"content\">");
/* User want to create an autorecording, ask for supplemental fields
and add some buttons */
/* Name of recording */
tcp_qprintf(&tq,
"<br><div>"
"<span style=\"text-align: right; width: 120px; float: left\">"
"Recording name:"
"</span>"
"<span style=\"width: 275px; float: left\">"
"<input type=\"text\" size=40 name=\"ar_name\""
" style=\"border: 1px dotted #000000\"> "
"</span>");
/* Priority of recorded events */
tcp_qprintf(&tq,
"<span style=\"text-align: right; width: 120px; float: left\">"
"Priority:"
"</span>"
"<span style=\"width: 275px; float: left\">"
"<input type=\"text\" size=5 name=\"ar_prio\""
" style=\"border: 1px dotted #000000\"> "
"</span></div><br><br>");
/* Create button */
tcp_qprintf(&tq,
"<div>"
"<span style=\"text-align: right; width: 120px; float: left\">"
"<p></span>"
"<span style=\"width: 275px; float: left\">"
"<input type=\"submit\" class=\"knapp\" name=\"ar_create\" "
"value=\"Create autorecording rule\">"
"</span>");
/* Cancel button */
tcp_qprintf(&tq,
"<span style=\"text-align: right; width: 120px; float: left\">"
"<p></span>"
"<span style=\"width: 275px; float: left\">"
"<input type=\"submit\" class=\"knapp\" name=\"ar_cancel\" "
"value=\"Cancel\">"
"</span></div><br><br>");
} else {
/* Search button */
tcp_qprintf(&tq,
"<center><input type=\"submit\" class=\"knapp\" name=\"s\" "
"value=\"Search\"></center>");
}
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "<br>");
/* output search result, if we've done a query */
if(search != NULL || autorec != NULL) {
box_top(&tq, "box");
tcp_qprintf(&tq,
"<div class=\"content\">");
epg_lock();
LIST_INIT(&events);
c = epg_search(&events, title, s_ecg, s_tcg, s_ch);
if(c == -1) {
tcp_qprintf(&tq,
"<center><b>"
"Event title: Regular expression syntax error"
"<b></center>");
} else if(c == 0) {
tcp_qprintf(&tq,
"<center><b>"
"No matching entries found"
"<b></center>");
} else {
tcp_qprintf(&tq,
"<div>"
"<span style=\"text-align: left; width: 630px; "
"float: left;font-weight:bold\"> %d entries found", c);
ev = alloca(c * sizeof(event_t *));
c = 0;
LIST_FOREACH(e, &events, e_tmp_link)
ev[c++] = e;
qsort(ev, c, sizeof(event_t *), eventcmp);
if(c > 100) {
c = 100;
tcp_qprintf(&tq,
", %d entries shown", c);
}
if(autorec == NULL)
tcp_qprintf(&tq,
"</span>"
"<span style=\"float: left\">"
"<input type=\"submit\" class=\"knapp\" name=\"ar\" "
"value=\"Create autorecording\">");
tcp_qprintf(&tq, "</span></div>");
memset(&day, -1, sizeof(struct tm));
for(k = 0; k < c; k++) {
e = ev[k];
localtime_r(&e->e_start, &a);
if(a.tm_wday != day.tm_wday || a.tm_mday != day.tm_mday ||
a.tm_mon != day.tm_mon || a.tm_year != day.tm_year) {
memcpy(&day, &a, sizeof(struct tm));
tcp_qprintf(&tq,
"<br><b><i>%s, %d/%d</i></b><br>",
days[day.tm_wday], day.tm_mday, day.tm_mon + 1);
}
output_event(hc, &tq, e->e_ch, e, simple, 1, 1, 600);
}
}
epg_unlock();
tcp_qprintf(&tq, "</div>");
box_bottom(&tq);
tcp_qprintf(&tq, "</form>");
}
html_footer(&tq);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* XML output
*/
static int
xml_channellist(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_channel_t *ch;
th_channel_group_t *tcg;
struct sockaddr_in *si;
char escapebuf[100];
if(!html_verify_access(hc, "browse-events"))
return HTTP_STATUS_UNAUTHORIZED;
tcp_init_queue(&tq, -1);
si = (struct sockaddr_in *)&hc->hc_tcp_session.tcp_self_addr;
tcp_qprintf(&tq,
"<?xml version='1.0' encoding='utf-8'?>\n"
"<all-channel-groups>\n");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
esacpe_char(escapebuf, sizeof(escapebuf), tcg->tcg_name, '&', "");
tcp_qprintf(&tq,
" <channel-group id=\"%d\">\n"
" <name>%s</name>\n",
tcg->tcg_tag, escapebuf);
TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link) {
if(LIST_FIRST(&ch->ch_transports) == NULL)
continue;
esacpe_char(escapebuf, sizeof(escapebuf), ch->ch_name, '&', "");
tcp_qprintf(&tq, " <channel id=\"%d\">\n", ch->ch_tag);
tcp_qprintf(&tq, " <name>%s</name>\n", escapebuf);
if(ch->ch_icon != NULL)
tcp_qprintf(&tq, " <icon>%s</icon>\n", refstr_get(ch->ch_icon));
tcp_qprintf(&tq, " <mimetype>video/mpeg</mimetype>\n");
tcp_qprintf(&tq, " <url>rtsp://%s:%d/%s</url>\n",
inet_ntoa(si->sin_addr), ntohs(si->sin_port),
ch->ch_sname);
tcp_qprintf(&tq, " </channel>\n");
}
tcp_qprintf(&tq, " </channel-group>\n");
}
tcp_qprintf(&tq,
"</all-channel-groups>\n");
http_output_queue(hc, &tq, "text/xml; charset=UTF-8", 0);
return 0;
}
/**
* HTML user interface setup code
*/
void
htmlui_start(void)
{
http_path_add("/", NULL, page_root);
http_path_add("/event", NULL, page_event);
http_path_add("/channel", NULL, page_channel);
http_path_add("/pvr", NULL, page_pvr);
http_path_add("/status", NULL, page_status);
http_path_add("/chgrp", NULL, page_chgroups);
http_path_add("/chadm", NULL, page_chadm);
http_path_add("/chadm2", NULL, page_chadm2);
http_path_add("/editchannel", NULL, page_editchannel);
http_path_add("/updatechannel", NULL, page_updatechannel);
http_path_add("/css.css", NULL, page_css);
http_path_add("/search", NULL, page_search);
http_path_add("/channellist.xml", NULL, xml_channellist);
}