From 448fe2faf96de19f1e4b744bc3df405a1a5b115b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Tue, 12 Feb 2008 14:28:30 +0000 Subject: [PATCH] Replace the 'content guide' with a much more powerful search tab --- epg.c | 88 ++++++++++++- epg.h | 6 + htmlui.c | 389 +++++++++++++++++++++++++++++++++++-------------------- tvhead.h | 9 +- 4 files changed, 347 insertions(+), 145 deletions(-) diff --git a/epg.c b/epg.c index 010042c6..1cf6ba22 100644 --- a/epg.c +++ b/epg.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "tvhead.h" #include "channels.h" @@ -488,6 +489,7 @@ epg_transfer_events(th_channel_t *ch, struct event_queue *src, } static const char *groupnames[16] = { + [0] = "Unclassified", [1] = "Movie / Drama", [2] = "News / Current affairs", [3] = "Show / Games", @@ -516,12 +518,13 @@ epg_content_type_find_by_dvbcode(uint8_t dvbcode) ecg = epg_content_groups[group]; if(ecg == NULL) { ecg = epg_content_groups[group] = calloc(1, sizeof(epg_content_group_t)); - ecg->ecg_name = groupnames[group] ?: "Unknown"; + ecg->ecg_name = groupnames[group]; } ect = ecg->ecg_types[type]; if(ect == NULL) { ect = ecg->ecg_types[type] = calloc(1, sizeof(epg_content_type_t)); + ect->ect_group = ecg; snprintf(buf, sizeof(buf), "type%d", type); ect->ect_name = strdup(buf); } @@ -529,14 +532,97 @@ epg_content_type_find_by_dvbcode(uint8_t dvbcode) return ect; } +/** + * + */ +epg_content_group_t * +epg_content_group_find_by_name(const char *name) +{ + epg_content_group_t *ecg; + int i; + + for(i = 0; i < 16; i++) { + ecg = epg_content_groups[i]; + if(ecg->ecg_name && !strcmp(name, ecg->ecg_name)) + return ecg; + } + return NULL; +} +/** + * Given the arguments, search all EPG events and enlist them + * + * XXX: Optimize if we know channel, group, etc + */ +int +epg_search(struct event_list *h, const char *title, epg_content_group_t *s_ecg, + th_channel_group_t *s_tcg, th_channel_t *s_ch) +{ + th_channel_group_t *dis; + th_channel_t *ch; + event_t *e; + int num = 0; + regex_t preg; + + if(title != NULL && + regcomp(&preg, title, REG_ICASE | REG_EXTENDED | REG_NOSUB)) + return -1; + + dis = channel_group_find("-disabled-", 1); + + LIST_FOREACH(ch, &channels, ch_global_link) { + if(ch->ch_group == dis) + continue; + + if(LIST_FIRST(&ch->ch_transports) == NULL) + continue; + + if(s_ch != NULL && s_ch != ch) + continue; + + if(s_tcg != NULL && s_tcg != ch->ch_group) + continue; + + TAILQ_FOREACH(e, &ch->ch_epg_events, e_link) { + + if(e->e_start + e->e_duration < dispatch_clock) + continue; /* old */ + + if(s_ecg != NULL) { + if(e->e_content_type == NULL) + continue; + + if(e->e_content_type->ect_group != s_ecg) + continue; + } + + if(title != NULL) { + if(regexec(&preg, e->e_title, 0, NULL, 0)) + continue; + } + + LIST_INSERT_HEAD(h, e, e_tmp_link); + num++; + } + } + if(title != NULL) + regfree(&preg); + + return num; +} + /* * */ void epg_init(void) { + int i; + + for(i = 0x0; i < 0x100; i+=16) + epg_content_type_find_by_dvbcode(i); + dtimer_arm(&epg_channel_maintain_timer, epg_channel_maintain, NULL, 5); } diff --git a/epg.h b/epg.h index ec27a1a9..3de5d05d 100644 --- a/epg.h +++ b/epg.h @@ -56,4 +56,10 @@ event_t *epg_event_find_current_or_upcoming(th_channel_t *ch); epg_content_type_t *epg_content_type_find_by_dvbcode(uint8_t dvbcode); +epg_content_group_t *epg_content_group_find_by_name(const char *name); + +int epg_search(struct event_list *h, const char *title, + epg_content_group_t *s_ecg, th_channel_group_t *s_tcg, + th_channel_t *s_ch); + #endif /* EPG_H */ diff --git a/htmlui.c b/htmlui.c index 476838b4..141ac412 100644 --- a/htmlui.c +++ b/htmlui.c @@ -67,6 +67,19 @@ static struct strtab recstatuscolor[] = { }; +/** + * 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 */ @@ -147,6 +160,9 @@ page_css(http_connection_t *hc, const char *remain, void *opaque) "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; " @@ -179,7 +195,7 @@ page_css(http_connection_t *hc, const char *remain, void *opaque) static void html_header(tcp_queue_t *tq, const char *title, int javascript, int width, - int autorefresh) + int autorefresh, const char *extrascript) { char w[30]; @@ -220,6 +236,10 @@ html_header(tcp_queue_t *tq, const char *title, int javascript, int width, "\r\n"); } + + if(extrascript) + tcp_qprintf(tq, "%s", extrascript); + /* BODY start */ tcp_qprintf(tq, "\r\n"); @@ -279,11 +299,11 @@ top_menu(http_connection_t *hc, tcp_queue_t *tq) tcp_qprintf(tq, "
  • Channel Guide
  • "); - tcp_qprintf(tq, "
  • Content Guide
  • "); + tcp_qprintf(tq, "
  • Search
  • "); if(html_verify_access(hc, "record-events")) - tcp_qprintf(tq, "
  • Recordings
  • "); - + tcp_qprintf(tq, "
  • Recorder log
  • "); + if(html_verify_access(hc, "system-status")) tcp_qprintf(tq, "
  • System Status
  • "); @@ -349,7 +369,8 @@ is_client_simple(http_connection_t *hc) static void output_event(http_connection_t *hc, tcp_queue_t *tq, th_channel_t *ch, - event_t *e, int simple, int print_channel) + event_t *e, int simple, int print_channel, int print_pvr, + int maxsize) { char title[100]; char bufa[4000]; @@ -393,27 +414,29 @@ output_event(http_connection_t *hc, tcp_queue_t *tq, th_channel_t *ch, esacpe_char(title, sizeof(title), ch->ch_name, '"', "'"); tcp_qprintf(tq, - "" + "" "%s", - simple ? 80 : 150, title); + simple ? 80 : maxsize * 125 / 600, title); } esacpe_char(title, sizeof(title), e->e_title, '"', "'"); tcp_qprintf(tq, - "" + "" "%02d:%02d - %02d:%02d" "%s", - simple ? 80 : 100, + simple ? 80 : maxsize * 125 / 600, e == cur ? ";font-weight:bold" : "", a.tm_hour, a.tm_min, b.tm_hour, b.tm_min, - simple ? 100 : 330, + simple ? 100 : maxsize * 350 / 600, e == cur ? ";font-weight:bold" : "", title ); - if(!pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color)) { + if(print_pvr && !pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color)) { tcp_qprintf(tq, "%s
    ", @@ -457,7 +480,7 @@ page_root(http_connection_t *hc, const char *remain, void *opaque) if(i < 30) i = 30; - html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, i); + html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, i, NULL); top_menu(hc, &tq); TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) { @@ -509,7 +532,7 @@ page_root(http_connection_t *hc, const char *remain, void *opaque) for(i = 0; i < 3 && e != NULL; i++) { - output_event(hc, &tq, ch, e, simple, 0); + output_event(hc, &tq, ch, e, simple, 0, 1, 580); e = TAILQ_NEXT(e, e_link); } @@ -570,7 +593,7 @@ page_channel(http_connection_t *hc, const char *remain, void *opaque) tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0); + html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL); top_menu(hc, &tq); @@ -602,7 +625,7 @@ page_channel(http_connection_t *hc, const char *remain, void *opaque) break; if(a.tm_wday == wday + doff) { - output_event(hc, &tq, ch, e, simple, 0); + output_event(hc, &tq, ch, e, simple, 0, 1, 580); } e = TAILQ_NEXT(e, e_link); } @@ -687,7 +710,7 @@ page_event(http_connection_t *hc, const char *remain, void *opaque) tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0); + html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0, NULL); top_menu(hc, &tq); tcp_qprintf(&tq, "
    ", eventid); @@ -793,7 +816,7 @@ page_pvrlog(http_connection_t *hc, const char *remain, void *opaque) return HTTP_STATUS_UNAUTHORIZED; tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0); + html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0, NULL); top_menu(hc, &tq); box_top(&tq, "box"); @@ -900,122 +923,6 @@ page_pvrlog(http_connection_t *hc, const char *remain, void *opaque) return 0; } -/** - * Content listing - */ - - -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; -} - -static int -page_contentlist(http_connection_t *hc, const char *remain, void *opaque) -{ - tcp_queue_t tq; - int simple = is_client_simple(hc); - event_t *e, **ev; - int c, i, j, k; - struct tm a, day; - th_channel_t *ch; - epg_content_group_t *ecg; - epg_content_type_t *ect; - th_channel_group_t *dis; - struct event_list events; - - dis = channel_group_find("-disabled-", 1); - - tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0); - top_menu(hc, &tq); - - epg_lock(); - - for(i = 1; i < 17; i++) { - if((ecg = epg_content_groups[i & 0xf]) == NULL) - continue; - - LIST_INIT(&events); - - - c = 0; - for(j = 0; j < 16; j++) { - if((ect = ecg->ecg_types[j]) == NULL) - continue; - LIST_FOREACH(e, &ect->ect_events, e_content_type_link) { - ch = e->e_ch; - if(ch->ch_group == dis) - continue; - if(e->e_start + e->e_duration < dispatch_clock) - continue; - - LIST_INSERT_HEAD(&events, e, e_tmp_link); - c++; - } - } - - if(c == 0) - continue; - - 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); - - box_top(&tq, "box"); - - tcp_qprintf(&tq, - "
    " - "
    %s
    ", ecg->ecg_name); - tcp_qprintf(&tq, "
    "); - - if(c > 25) - c = 25; - - 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); - } - tcp_qprintf(&tq, "
    "); - box_bottom(&tq); - tcp_qprintf(&tq, "
    \r\n"); - } - - epg_unlock(); - - tcp_qprintf(&tq, "
    \r\n"); - html_footer(&tq); - http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0); - - return 0; -} @@ -1062,7 +969,7 @@ page_status(http_connection_t *hc, const char *remain, void *opaque) tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", !simple, -1, 0); + html_header(&tq, "HTS/tvheadend", !simple, -1, 0, NULL); top_menu(hc, &tq); @@ -1405,7 +1312,7 @@ page_chgroups(http_connection_t *hc, const char *remain, void *opaque) tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0); + html_header(&tq, "HTS/tvheadend", 0, MAIN_WIDTH, 0, NULL); top_menu(hc, &tq); @@ -1475,7 +1382,7 @@ page_chadm(http_connection_t *hc, const char *remain, void *opaque) return HTTP_STATUS_UNAUTHORIZED; tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0); + html_header(&tq, "HTS/tvheadend", !simple, MAIN_WIDTH, 0, NULL); top_menu(hc, &tq); tcp_qprintf(&tq, @@ -1516,7 +1423,7 @@ page_chadm2(http_connection_t *hc, const char *remain, void *opaque) return HTTP_STATUS_UNAUTHORIZED; tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", !simple, 230, 0); + html_header(&tq, "HTS/tvheadend", !simple, 230, 0, NULL); LIST_FOREACH(ch, &channels, ch_global_link) { tcp_qprintf(&tq, @@ -1550,7 +1457,7 @@ page_editchannel(http_connection_t *hc, const char *remain, void *opaque) dis = channel_group_find("-disabled-", 1); tcp_init_queue(&tq, -1); - html_header(&tq, "HTS/tvheadend", 0, 530, 0); + html_header(&tq, "HTS/tvheadend", 0, 530, 0, NULL); tcp_qprintf(&tq, "", @@ -1740,6 +1647,209 @@ page_updatechannel(http_connection_t *hc, const char *remain, void *opaque) } +/** + * 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_url_args, "s"); + const char *title = http_arg_get(&hc->hc_url_args, "n"); + const char *content = http_arg_get(&hc->hc_url_args, "c"); + const char *chgroup = http_arg_get(&hc->hc_url_args, "g"); + const char *channel = http_arg_get(&hc->hc_url_args, "ch"); + + 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; + + 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; + + 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:" + "" + "" + "

    "); + + /* 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) { + + 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 { + + 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 > 25) + c = 25; + + 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, 700); + } + } + epg_unlock(); + tcp_qprintf(&tq, "
    "); + box_bottom(&tq); + } + + + html_footer(&tq); + http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0); + return 0; +} + + /** * HTML user interface setup code @@ -1758,6 +1868,5 @@ htmlui_start(void) 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("/contentlist", NULL, page_contentlist); - + http_path_add("/search", NULL, page_search); } diff --git a/tvhead.h b/tvhead.h index 46348c62..a6286937 100644 --- a/tvhead.h +++ b/tvhead.h @@ -783,10 +783,6 @@ typedef struct th_subscription { * * Based on the content types defined in EN 300 468 */ -typedef struct epg_content_type { - const char *ect_name; - struct event_list ect_events; -} epg_content_type_t; typedef struct epg_content_group { @@ -794,6 +790,11 @@ typedef struct epg_content_group { struct epg_content_type *ecg_types[16]; } epg_content_group_t; +typedef struct epg_content_type { + const char *ect_name; + struct event_list ect_events; + epg_content_group_t *ect_group; +} epg_content_type_t; /* * EPG event