/* * 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" #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, "\r\n" "\r\n" "%s\r\n" "\r\n", title); if(autorefresh) tcp_qprintf(tq, "\r\n", autorefresh); tcp_qprintf(tq, "" ); tcp_qprintf(tq, "", w); if(javascript) { tcp_qprintf(tq, "\r\n"); } if(extrascript) tcp_qprintf(tq, "%s", extrascript); /* 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(http_connection_t *hc, tcp_queue_t *tq) { tcp_qprintf(tq, "
", MAIN_WIDTH); box_top(tq, "box"); tcp_qprintf(tq, "
" "
    "); tcp_qprintf(tq, "
  • Channel Guide
  • "); tcp_qprintf(tq, "
  • Search
  • "); if(html_verify_access(hc, "record-events")) tcp_qprintf(tq, "
  • Recorder
  • "); if(html_verify_access(hc, "system-status")) tcp_qprintf(tq, "
  • System Status
  • "); if(html_verify_access(hc, "admin")) tcp_qprintf(tq, "
  • Manage channel groups
  • "); if(html_verify_access(hc, "admin")) tcp_qprintf(tq, "
  • Manage channels
  • "); tcp_qprintf(tq, "
"); box_bottom(tq); tcp_qprintf(tq, "

\r\n"); if(sys_warning != NULL) { box_top(tq, "box"); tcp_qprintf(tq, "
" "" "Warning: %s
", sys_warning); 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, 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, "
", 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); 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, "
%s
", tcg->tcg_name); box_bottom(&tq); tcp_qprintf(&tq, "
"); TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link) { if(LIST_FIRST(&ch->ch_transports) == NULL) continue; 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); si = (struct sockaddr_in *)&hc->hc_tcp_session.tcp_self_addr; tcp_qprintf(&tq, "Watch live
", 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, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
\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, "
"); 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, 0, 1, 580); } 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", 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, "
", 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,"
"); if(html_verify_access(hc, "record-events")) { switch(pvrstatus) { case HTSTV_PVR_STATUS_SCHEDULED: tcp_qprintf(&tq, ""); break; 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", 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, "\n"); top_menu(hc, &tq); tcp_qprintf(&tq, "
"); /* * PVR log */ box_top(&tq, "box"); tcp_qprintf(&tq, "
%s
", "Recorder Log"); tcp_qprintf(&tq, "
"); c = 0; LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) c++; if(c == 0) { tcp_qprintf(&tq, "
No entries

"); } 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, "
%s, %d/%d
", days[day.tm_wday], day.tm_mday, day.tm_mon + 1); } tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
", divid, pvrr_tgt == pvrr ? "block" : "none"); tcp_qprintf(&tq, ""); tcp_qprintf(&tq, "" "" "", pvrr->pvrr_creator ?: "not set"); tcp_qprintf(&tq, "" "" "", pvrr->pvrr_filename ?: "not set"); tcp_qprintf(&tq, "" "" "", val2str(pvrr->pvrr_tffm.tffm_state, recintstatustxt) ?: "invalid"); tcp_qprintf(&tq, "
" "Created by:" "%s
" "Filename:" "%s
" "Recorder status:" "%s
"); 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,"
" "
", buttonname, buttontxt); tcp_qprintf(&tq, "
\n"); divid++; } tcp_qprintf(&tq, "
" "
"); tcp_qprintf(&tq, "
\r\n"); box_bottom(&tq); tcp_qprintf(&tq, "
\r\n"); /* * Autorecorder */ box_top(&tq, "box"); tcp_qprintf(&tq, "
%s
", "Autorecorder ruleset"); tcp_qprintf(&tq, "
\r\n"); if(TAILQ_FIRST(&autorecs) == NULL) tcp_qprintf(&tq, "
No entries

"); TAILQ_FOREACH(ar, &autorecs, ar_link) { tcp_qprintf(&tq, "
\r\n", divid, ar->ar_name); tcp_qprintf(&tq, "
", divid, ar_show == ar ? "block" : "none"); tcp_qprintf(&tq, ""); tcp_qprintf(&tq, "" "" "", ar->ar_creator ?: "not set"); tcp_qprintf(&tq, "" "" "", ar->ar_rec_prio); tcp_qprintf(&tq, "" "" "", ar->ar_title ?: "All"); tcp_qprintf(&tq, "" "" "", ar->ar_ecg ? ar->ar_ecg->ecg_name: "All"); tcp_qprintf(&tq, "" "" "", ar->ar_tcg ? ar->ar_tcg->tcg_name: "All"); tcp_qprintf(&tq, "" "" "", ar->ar_ch ? ar->ar_ch->ch_name: "All"); tcp_qprintf(&tq, "
" "Created by:" "%s
" "Recording priority:" "%d
" "Event tile:" "%s
" "Content group:" "%s
" "Channel group:" "%s
" "Channel:" "%s
"); tcp_qprintf(&tq,"
" "
", ar->ar_id); tcp_qprintf(&tq, "
\n"); divid++; } 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", 0); return 0; } static void html_iptv_status(tcp_queue_t *tq, th_transport_t *t, const char *status) { tcp_qprintf(tq, "" "%s (%s)" "", inet_ntoa(t->tht_iptv_group_addr), t->tht_channel->ch_name); tcp_qprintf(tq, "" "%s" "
", 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, "
"); tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
Input devices
"); tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); /* DVB adapters */ box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
DVB adapters
"); if(LIST_FIRST(&dvb_adapters_running) == NULL) { tcp_qprintf(&tq, "No DVB adapters configured
"); } else { LIST_FOREACH(tda, &dvb_adapters_running, tda_link) { tcp_qprintf(&tq, "
%s
%s
", tda->tda_rootpath, tda->tda_info); LIST_FOREACH(tdmi, &tda->tda_muxes_active, tdmi_adapter_link) { tcp_qprintf(&tq, "" "%s" "", 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, "" "%s" "", 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, "" "%s" "", txt); tcp_qprintf(&tq, "" "%d" "
", vv); } } } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); /* IPTV adapters */ box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
IPTV sources
"); 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, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); /* Video4Linux adapters */ box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
Video4Linux adapters
"); LIST_FOREACH(tva, &v4l_adapters, tva_link) { tcp_qprintf(&tq, "" "%s" "", 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, "" "%s" "
", tmptxt); } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); /* Active transports */ tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
Active transports
"); tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); LIST_FOREACH(t, &all_transports, tht_global_link) { if(t->tht_status != TRANSPORT_RUNNING) continue; box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "" "%s" "" "" "%s" "
", 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, "" "%s" "" "" "%s" "

", t1, t2); LIST_FOREACH(st, &t->tht_streams, st_link) { tcp_qprintf(&tq, "" "%s" "", htstvstreamtype2txt(st->st_type)); tcp_qprintf(&tq, "" "%d kb/s" "", avgstat_read_and_expire(&st->st_rate, dispatch_clock) / 1000); tcp_qprintf(&tq, "" "%d errors/s" "
", avgstat_read_and_expire(&st->st_cc_errors, dispatch_clock)); } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); } tcp_qprintf(&tq, "
"); /* Subscribers */ tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
Subscriptions
"); tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); LIST_FOREACH(s, &subscriptions, ths_global_link) { box_top(&tq, "box"); tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "" "%s" "", s->ths_title); tcp_qprintf(&tq, "" "%s" "
", s->ths_channel->ch_name); if((t = s->ths_transport) == NULL) { tcp_qprintf(&tq, "No transport available
"); } else { tcp_qprintf(&tq, "Using transport \"%s\"
", t->tht_name); } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); } tcp_qprintf(&tq, "
"); tcp_qprintf(&tq, "
"); 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, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
"); cnt = 0; TAILQ_FOREACH(ch, &tcg->tcg_channels, ch_group_link) cnt++; tcp_qprintf(&tq, "%s (%d channels)

", tcg->tcg_name, cnt); tcp_qprintf(&tq, " ", tcg->tcg_tag); tcp_qprintf(&tq, " ", tcg->tcg_tag); if(tcg->tcg_cant_delete_me == 0) { tcp_qprintf(&tq, "", tcg->tcg_tag); } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
\r\n"); } tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
" " " "" "
"); box_bottom(&tq); tcp_qprintf(&tq, "

\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, " " "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
" "" "
"); box_bottom(&tq); tcp_qprintf(&tq, "
" "" "
"); 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, "%s
", 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, "
", ch->ch_tag); box_top(&tq, "box"); tcp_qprintf(&tq, "
%s
", ch->ch_name); box_bottom(&tq); tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
" "
" "Channel group: " "
" "" "", ch->ch_group == dis ? " selected" : "", dis->tcg_name); tcp_qprintf(&tq, "

\r\n"); tcp_qprintf(&tq, "
" "Merge with channel: " "
" "" "

\r\n"); tcp_qprintf(&tq, "
" "Teletext rundown page: " "
" "" "
", ch->ch_teletext_rundown); tcp_qprintf(&tq, "

\r\n"); tcp_qprintf(&tq, "" "
Transports

" ""); tcp_qprintf(&tq, "" ""); LIST_FOREACH(t, &ch->ch_transports, tht_channel_link) { tcp_qprintf(&tq, "" "" "" "" "" "" "", t->tht_uniquename, t->tht_prio, t->tht_scrambled ? "Yes" : "No", t->tht_provider, t->tht_network, t->tht_name); } tcp_qprintf(&tq, "
Priority" "Scrambled" "Provider" "Network" "Transport ID" "
" "" "
%s
%s
%s
%s
\r\n"); tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); 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, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "

" "Invalid / Missing arguments:

"); if(ar_name == NULL) tcp_qprintf(&tq, "- Name is missing

"); if(ar_prio == NULL || atoi(ar_prio) < 1) tcp_qprintf(&tq, "- Priority is missing or not a positive number

"); tcp_qprintf(&tq, "
"); 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, ""); box_top(&tq, "box"); tcp_qprintf(&tq, "
" "
"); /* Title */ tcp_qprintf(&tq, "
" "" "Event title:" "" "" " " ""); /* Specific content */ tcp_qprintf(&tq, "" "Content:" "" "" "

"); /* Specific channel group */ tcp_qprintf(&tq, "
" "" "Channel group:" "" "" ""); /* Specific channel */ tcp_qprintf(&tq, "" "Channel:" "" "" "


"); if(autorec != NULL) { tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); box_top(&tq, "box"); tcp_qprintf(&tq, "
"); /* User want to create an autorecording, ask for supplemental fields and add some buttons */ /* Name of recording */ tcp_qprintf(&tq, "
" "" "Recording name:" "" "" " " ""); /* Priority of recorded events */ tcp_qprintf(&tq, "" "Priority:" "" "" " " "


"); /* Create button */ tcp_qprintf(&tq, "
" "" "

" "" "" ""); /* Cancel button */ tcp_qprintf(&tq, "" "

" "" "" "



"); } else { /* Search button */ tcp_qprintf(&tq, "
"); } tcp_qprintf(&tq, "
"); box_bottom(&tq); tcp_qprintf(&tq, "
"); /* output search result, if we've done a query */ if(search != NULL || autorec != NULL) { box_top(&tq, "box"); tcp_qprintf(&tq, "
"); epg_lock(); LIST_INIT(&events); c = epg_search(&events, title, s_ecg, s_tcg, s_ch); if(c == -1) { tcp_qprintf(&tq, "
" "Event title: Regular expression syntax error" "
"); } else if(c == 0) { tcp_qprintf(&tq, "
" "No matching entries found" "
"); } else { tcp_qprintf(&tq, "
" " %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, "" "" ""); tcp_qprintf(&tq, "
"); 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, "
%s, %d/%d
", 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, "
"); box_bottom(&tq); tcp_qprintf(&tq, ""); } 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, "\n" "\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, " \n" " %s\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, " \n", ch->ch_tag); tcp_qprintf(&tq, " %s\n", escapebuf); if(ch->ch_icon != NULL) tcp_qprintf(&tq, " %s\n", refstr_get(ch->ch_icon)); tcp_qprintf(&tq, " video/mpeg\n"); tcp_qprintf(&tq, " rtsp://%s:%d/%s\n", inet_ntoa(si->sin_addr), ntohs(si->sin_port), ch->ch_sname); tcp_qprintf(&tq, " \n"); } tcp_qprintf(&tq, " \n"); } tcp_qprintf(&tq, "\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); }