diff --git a/Makefile b/Makefile index 25d3e780..096628c2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \ subscriptions.c tsmux.c tsdemux.c pes.c buffer.c tcp.c -SRCS += http.c +SRCS += http.c htmlui.c SRCS += pvr.c diff --git a/htmlui.c b/htmlui.c new file mode 100644 index 00000000..eb65abbc --- /dev/null +++ b/htmlui.c @@ -0,0 +1,753 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvhead.h" +#include "htmlui.h" +#include "channels.h" +#include "epg.h" +#include "pvr.h" +#include "strtab.h" + +static struct strtab recstatustxt[] = { + { "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 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 }, +}; + + +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; +} + + + + + +static void +html_header(tcp_queue_t *tq, const char *title, int javascript) +{ + tcp_qprintf(tq, + "\r\n" + "\r\n" + "%s\r\n" + "\r\n", title); + + tcp_qprintf(tq, + ""); + + if(javascript) { + tcp_qprintf(tq, + "\r\n"); + + + + tcp_qprintf(tq, + "\r\n"); + } + /* BODY start */ + + tcp_qprintf(tq, "\r\n"); + + if(javascript) + tcp_qprintf(tq, + "
\r\n"); + +} + +static void +html_footer(tcp_queue_t *tq) +{ + tcp_qprintf(tq, + "\r\n\r\n"); +} + + +static void +box_top(tcp_queue_t *tq, const char *style) +{ + tcp_qprintf(tq, "
" + "
" + "
" + "
" + "
" + "
" + "
", style); +} + +static void +box_bottom(tcp_queue_t *tq) +{ + tcp_qprintf(tq, + "
" + "
" + "
" + "
" + "
" + "
"); +} + + +static void +top_menu(tcp_queue_t *tq) +{ + box_top(tq, "box"); + + tcp_qprintf(tq, + "
" + "
"); + + box_bottom(tq); + tcp_qprintf(tq, "
\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) +{ + 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; + + localtime_r(&e->e_start, &a); + stop = e->e_start + e->e_duration; + localtime_r(&stop, &b); + + pvrstatus = pvr_prog_status(e); + + cur = epg_event_get_current(ch); + + if(!simple && e->e_desc != NULL) { + 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); + } + + esacpe_char(title, sizeof(title), e->e_title, '"', "'"); + + tcp_qprintf(tq, + "
" + "" + "" + "%02d:%02d - %02d:%02d" + "%s", + bufa, + overlibstuff, + simple ? 80 : 100, + e == cur ? ";font-weight:bold" : "", + a.tm_hour, a.tm_min, b.tm_hour, b.tm_min, + simple ? 100 : 250, + e == cur ? ";font-weight:bold" : "", + title + ); + + if(!pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color)) { + tcp_qprintf(tq, + "" + "%s

", + pvr_color, pvr_txt); + } else { + tcp_qprintf(tq, "
"); + } +} + +/* + * 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); + + + tcp_init_queue(&tq, -1); + + html_header(&tq, "HTS/tvheadend", !simple); + + top_menu(&tq); + + epg_lock(); + TAILQ_FOREACH(ch, &channels, ch_global_link) { + box_top(&tq, "box"); + tcp_qprintf(&tq, "
"); + + if(!simple) { + tcp_qprintf(&tq, "
"); + if(ch->ch_icon) { + tcp_qprintf(&tq, "" + "" + "", + ch->ch_tag, + refstr_get(ch->ch_icon)); + } + tcp_qprintf(&tq, "
"); + } + + tcp_qprintf(&tq, "
"); + tcp_qprintf(&tq, "%s
", + ch->ch_tag, ch->ch_name); + + e = epg_event_find_current_or_upcoming(ch); + + for(i = 0; i < 3 && e != NULL; i++) { + output_event(hc, &tq, ch, e, simple); + e = TAILQ_NEXT(e, e_link); + } + + tcp_qprintf(&tq, "
"); + box_bottom(&tq); + tcp_qprintf(&tq, "
\r\n"); + } + epg_unlock(); + + html_footer(&tq); + http_output_queue(hc, &tq, "text/html; charset=UTF-8"); + 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; + + 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); + + top_menu(&tq); + + epg_lock(); + + box_top(&tq, "box"); + + tcp_qprintf(&tq, "
"); + tcp_qprintf(&tq, "%s
", + 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, + "%s
", + 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); + e = TAILQ_NEXT(e, e_link); + } + } + } + + tcp_qprintf(&tq, "
"); + box_bottom(&tq); + tcp_qprintf(&tq, "
\r\n"); + epg_unlock(); + + html_footer(&tq); + http_output_queue(hc, &tq, "text/html; charset=UTF-8"); + 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]; + recop_t cmd = 0; + tv_pvr_status_t pvrstatus; + const char *pvr_txt, *pvr_color; + + epg_lock(); + e = epg_event_find_by_tag(eventid); + if(e == NULL) { + epg_unlock(); + return 404; + } + + remain = strchr(remain, '?'); + if(remain != NULL) { + remain++; + if(!strncmp(remain, "rec=", 4)) + cmd = RECOP_ONCE; + if(!strncmp(remain, "cancel=", 7)) + cmd = RECOP_CANCEL; + pvr_event_record_op(e->e_ch, e, cmd); + + } + + pvrstatus = pvr_prog_status(e); + + 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); + top_menu(&tq); + + tcp_qprintf(&tq, "
", eventid); + + + box_top(&tq, "box"); + + tcp_qprintf(&tq, "
"); + + + esacpe_char(title, sizeof(title), e->e_title, '"', "'"); + esacpe_char(desc, sizeof(desc), e->e_desc, '\'', ""); + + tcp_qprintf(&tq, + "
" + "%02d:%02d - %02d:%02d
" + "
" + "%s
", + 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, + "
" + "%s
", + pvr_color, pvr_txt); + else + tcp_qprintf(&tq, "
"); + + + tcp_qprintf(&tq, "
%s", desc); + + tcp_qprintf(&tq,"
"); + + switch(pvrstatus) { + case HTSTV_PVR_STATUS_SCHEDULED: + case HTSTV_PVR_STATUS_RECORDING: + tcp_qprintf(&tq, + ""); + break; + + + case HTSTV_PVR_STATUS_NONE: + tcp_qprintf(&tq, + ""); + break; + + default: + tcp_qprintf(&tq, + ""); + break; + + } + + tcp_qprintf(&tq, "
"); + box_bottom(&tq); + tcp_qprintf(&tq, "
\r\n"); + html_footer(&tq); + http_output_queue(hc, &tq, "text/html; charset=UTF-8"); + + 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; +} + +/** + * Event page + */ +static int +page_pvrlog(http_connection_t *hc, const char *remain, void *opaque) +{ + tcp_queue_t tq; + int simple = is_client_simple(hc); + pvr_rec_t *pvrr; + event_t *e; + char escapebuf[4000], href[4000]; + char title[100]; + char channel[100]; + struct tm a, b, day; + const char *pvr_txt, *pvr_color; + int c, i; + pvr_rec_t **pv; + + tcp_init_queue(&tq, -1); + html_header(&tq, "HTS/tvheadend", 0); + top_menu(&tq); + + box_top(&tq, "box"); + + epg_lock(); + + tcp_qprintf(&tq, "
"); + + c = 0; + LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) + c++; + + 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]; + + e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start); + + if(e != NULL && !simple && e->e_desc != NULL) { + esacpe_char(escapebuf, sizeof(escapebuf), e->e_desc, '\'', ""); + + snprintf(href, sizeof(href), + "", + e->e_tag, + escapebuf); + + } else { + href[0] = 0; + } + + esacpe_char(channel, sizeof(channel), + pvrr->pvrr_channel->ch_name, '"', "'"); + + esacpe_char(title, sizeof(title), + pvrr->pvrr_title ?: "Unnamed recording", '"', "'"); + + 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, + "
%s, %d/%d
", + days[day.tm_wday], + day.tm_mday, + day.tm_mon + 1); + } + + + tcp_qprintf(&tq, + "%s" + "
" + "%s
" + "
" + "%02d:%02d - %02d:%02d
" + "
%s
%s", + href, + simple ? 80 : 100, + channel, + simple ? 80 : 100, + a.tm_hour, a.tm_min, b.tm_hour, b.tm_min, + simple ? 100 : 250, + title, + href[0] ? "
" : ""); + + if(!pvrstatus_to_html(pvrr->pvrr_status, &pvr_txt, &pvr_color)) { + tcp_qprintf(&tq, + "
" + "%s
", + pvr_color, pvr_txt); + } else { + tcp_qprintf(&tq, "
"); + } + } + + epg_unlock(); + tcp_qprintf(&tq, "
\r\n"); + + box_bottom(&tq); + tcp_qprintf(&tq, "\r\n"); + html_footer(&tq); + http_output_queue(hc, &tq, "text/html; charset=UTF-8"); + + 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("/pvrlog", NULL, page_pvrlog); +} diff --git a/htmlui.h b/htmlui.h new file mode 100644 index 00000000..dcf0f57d --- /dev/null +++ b/htmlui.h @@ -0,0 +1,26 @@ +/* + * 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 . + */ + +#ifndef HTMLUI_H_ +#define HTMLUI_H_ + +#include "http.h" + +void htmlui_start(void); + +#endif /* HTMLUI_H_ */