diff --git a/src/api.h b/src/api.h index c80a2f4c..08e0869d 100644 --- a/src/api.h +++ b/src/api.h @@ -25,7 +25,7 @@ #include "redblack.h" #include "access.h" -#define TVH_API_VERSION 14 +#define TVH_API_VERSION 15 /* * Command hook diff --git a/src/api/api_epg.c b/src/api/api_epg.c index d1ac6ef6..1fc74b82 100644 --- a/src/api/api_epg.c +++ b/src/api/api_epg.c @@ -22,8 +22,52 @@ #include "access.h" #include "api.h" #include "epg.h" +#include "imagecache.h" #include "dvr/dvr.h" +static htsmsg_t * +api_epg_get_list ( const char *s ) +{ + htsmsg_t *m = NULL; + char *r, *saveptr; + if (s && s[0] != '\0') { + s = r = strdup(s); + r = strtok_r(r, ";", &saveptr); + while (r) { + while (*r != '\0' && *r <= ' ') + r++; + if (*r != '\0') { + if (m == NULL) + m = htsmsg_create_list(); + htsmsg_add_str(m, NULL, r); + } + r = strtok_r(NULL, ";", &saveptr); + } + free((char *)s); + } + return m; +} + +static void +api_epg_add_channel ( htsmsg_t *m, channel_t *ch ) +{ + int64_t chnum; + char buf[32]; + htsmsg_add_str(m, "channelName", channel_get_name(ch)); + htsmsg_add_str(m, "channelUuid", channel_get_uuid(ch)); + if ((chnum = channel_get_number(ch)) >= 0) { + uint32_t maj = chnum / CHANNEL_SPLIT; + uint32_t min = chnum % CHANNEL_SPLIT; + if (min) + snprintf(buf, sizeof(buf), "%u.%u", maj, min); + else + snprintf(buf, sizeof(buf), "%u", maj); + htsmsg_add_str(m, "channelNumber", buf); + } + if (ch->ch_icon) + htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); +} + static htsmsg_t * api_epg_entry ( epg_broadcast_t *eb, const char *lang ) { @@ -31,8 +75,9 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang ) char buf[64]; epg_episode_t *ee = eb->episode; channel_t *ch = eb->channel; - htsmsg_t *m; + htsmsg_t *m, *m2; epg_episode_num_t epnum; + epg_genre_t *eg; dvr_entry_t *de; if (!ee || !ch) return NULL; @@ -40,8 +85,6 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang ) m = htsmsg_create_map(); /* EPG IDs */ - // Note: "id" is for UI compat, remove it? - htsmsg_add_u32(m, "id", eb->id); htsmsg_add_u32(m, "eventId", eb->id); if (ee) { htsmsg_add_u32(m, "episodeId", ee->id); @@ -55,25 +98,17 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang ) } /* Channel Info */ - // Note: "channel" is for UI compat, remove it? - htsmsg_add_str(m, "channel", channel_get_name(ch)); - htsmsg_add_str(m, "channelName", channel_get_name(ch)); - htsmsg_add_str(m, "channelUuid", channel_get_uuid(ch)); - htsmsg_add_u32(m, "channelId", channel_get_id(ch)); + api_epg_add_channel(m, ch); /* Time */ htsmsg_add_s64(m, "start", eb->start); htsmsg_add_s64(m, "stop", eb->stop); - htsmsg_add_s64(m, "duration", eb->stop - eb->start); - // TODO: the above can be removed /* Title/description */ if ((s = epg_broadcast_get_title(eb, lang))) htsmsg_add_str(m, "title", s); -#if TODO if ((s = epg_broadcast_get_subtitle(eb, lang))) htsmsg_add_str(m, "subtitle", s); -#endif if ((s = epg_broadcast_get_summary(eb, lang))) htsmsg_add_str(m, "summary", s); if ((s = epg_broadcast_get_description(eb, lang))) @@ -108,11 +143,29 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang ) /* Image */ if (ee->image) htsmsg_add_str(m, "image", ee->image); + + /* Rating */ + if (ee->star_rating) + htsmsg_add_u32(m, "starRating", ee->star_rating); + if (ee->age_rating) + htsmsg_add_u32(m, "ageRating", ee->age_rating); + + /* Content Type */ + m2 = NULL; + LIST_FOREACH(eg, &ee->genre, link) { + if (m2 == NULL) + m2 = htsmsg_create_list(); + htsmsg_add_u32(m2, NULL, eg->code); + } + if (m2) + htsmsg_add_msg(m, "genre", m2); } - + /* Recording */ - if ((de = dvr_entry_find_by_event(eb))) - htsmsg_add_str(m, "dvrId", idnode_uuid_as_str(&de->de_id)); + if ((de = dvr_entry_find_by_event(eb))) { + htsmsg_add_str(m, "dvrUuid", idnode_uuid_as_str(&de->de_id)); + htsmsg_add_str(m, "dvrState", dvr_entry_schedstatus(de)); + } /* Next event */ if ((eb = epg_broadcast_get_next(eb))) @@ -121,30 +174,245 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang ) return m; } +static void +api_epg_filter_set_str + ( epg_filter_str_t *f, const char *str, int comp ) +{ + f->str = strdup(str); + f->comp = comp; +} + +static void +api_epg_filter_add_str + ( epg_query_t *eq, const char *k, const char *v, int comp ) +{ + if (!strcmp(k, "channelName")) + api_epg_filter_set_str(&eq->channel_name, v, comp); + else if (!strcmp(k, "title")) + api_epg_filter_set_str(&eq->title, v, comp); + else if (!strcmp(k, "subtitle")) + api_epg_filter_set_str(&eq->subtitle, v, comp); + else if (!strcmp(k, "summary")) + api_epg_filter_set_str(&eq->summary, v, comp); + else if (!strcmp(k, "description")) + api_epg_filter_set_str(&eq->description, v, comp); +} + +static void +api_epg_filter_set_num + ( epg_filter_num_t *f, int64_t v1, int64_t v2, int comp ) +{ + /* Range? */ + if (f->comp == EC_LT && comp == EC_GT) { + f->val2 = f->val1; + f->val1 = v1; + f->comp = EC_RG; + return; + } + if (f->comp == EC_GT && comp == EC_LT) { + f->val2 = v1; + f->comp = EC_RG; + return; + } + f->val1 = v1; + f->val2 = v2; + f->comp = comp; +} + +static void +api_epg_filter_add_num + ( epg_query_t *eq, const char *k, int64_t v1, int64_t v2, int comp ) +{ + if (!strcmp(k, "start")) + api_epg_filter_set_num(&eq->start, v1, v2, comp); + else if (!strcmp(k, "stop")) + api_epg_filter_set_num(&eq->stop, v1, v2, comp); + else if (!strcmp(k, "duration")) + api_epg_filter_set_num(&eq->duration, v1, v2, comp); + else if (!strcmp(k, "episode")) + api_epg_filter_set_num(&eq->episode, v1, v2, comp); + else if (!strcmp(k, "stars")) + api_epg_filter_set_num(&eq->episode, v1, v2, comp); + else if (!strcmp(k, "age")) + api_epg_filter_set_num(&eq->age, v1, v2, comp); +} + +static struct strtab sortcmptab[] = { + { "start", ESK_START }, + { "stop", ESK_STOP }, + { "duration", ESK_DURATION }, + { "title", ESK_TITLE }, + { "subtitle", ESK_SUBTITLE }, + { "summary", ESK_SUMMARY }, + { "description", ESK_DESCRIPTION }, + { "channelName", ESK_CHANNEL }, + { "channelNumber", ESK_CHANNEL_NUM }, + { "starRating", ESK_STARS }, + { "ageRating", ESK_AGE }, + { "genre", ESK_GENRE } +}; + +static struct strtab filtcmptab[] = { + { "gt", EC_GT }, + { "lt", EC_LT }, + { "eq", EC_EQ }, + { "regex", EC_RE }, + { "range", EC_RG } +}; + +static int64_t +api_epg_decode_channel_num ( const char *s ) +{ + int64_t v = atol(s); + const char *s1 = strchr(s, '.'); + if (s1) + v += atol(s1 + 1) % CHANNEL_SPLIT; + return v; +} + static int api_epg_grid ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) { int i; - epg_query_result_t eqr; - const char *ch, *tag, *title, *lang/*, *genre*/; - uint32_t start, limit, end; - htsmsg_t *l = NULL, *e; - int min_duration; - int max_duration; + epg_query_t eq; + const char *lang, *str; + uint32_t start, limit, end, genre; + int64_t duration_min, duration_max; + htsmsg_field_t *f, *f2; + htsmsg_t *l = NULL, *e, *filter; *resp = htsmsg_create_map(); - /* Query params */ - ch = htsmsg_get_str(args, "channel"); - tag = htsmsg_get_str(args, "tag"); - //genre = htsmsg_get_str(args, "genre"); - title = htsmsg_get_str(args, "title"); - lang = htsmsg_get_str(args, "lang"); - // TODO: support multiple tag/genre/channel? + memset(&eq, 0, sizeof(eq)); - min_duration = htsmsg_get_u32_or_default(args, "minduration", 0); - max_duration = htsmsg_get_u32_or_default(args, "maxduration", INT_MAX); + lang = htsmsg_get_str(args, "lang"); + eq.lang = lang ? strdup(lang) : NULL; + + str = htsmsg_get_str(args, "title"); + if (str) + eq.stitle = strdup(str); + str = htsmsg_get_str(args, "channel"); + if (str) + eq.channel = strdup(str); + str = htsmsg_get_str(args, "channelTag"); + if (str) + eq.channel_tag = strdup(str); + + duration_min = -1; + duration_max = -1; + htsmsg_get_s64(args, "durationMin", &duration_min); + htsmsg_get_s64(args, "durationMax", &duration_max); + if (duration_min > 0 || duration_max > 0) { + eq.duration.comp = EC_RG; + eq.duration.val1 = duration_min < 0 ? 0 : duration_min; + eq.duration.val2 = duration_max < 0 ? 0 : duration_max; + } + + if (!htsmsg_get_u32(args, "contentType", &genre)) { + eq.genre = eq.genre_static; + eq.genre[0] = genre; + eq.genre_count = 1; + } + + /* Filter params */ + if ((filter = htsmsg_get_list(args, "filter"))) { + HTSMSG_FOREACH(f, filter) { + const char *k, *t, *v; + int comp; + if (!(e = htsmsg_get_map_by_field(f))) continue; + if (!(k = htsmsg_get_str(e, "field"))) continue; + if (!(t = htsmsg_get_str(e, "type"))) continue; + comp = str2val(htsmsg_get_str(e, "comparison") ?: "", filtcmptab); + if (comp == -1) comp = EC_EQ; + if (!strcmp(k, "channelNumber")) { + if (!strcmp(t, "numeric")) { + f2 = htsmsg_field_find(e, "value"); + if (f2) { + int64_t v1, v2 = 0; + if (f2->hmf_type == HMF_STR) { + const char *s = htsmsg_field_get_str(f2); + const char *z = s ? strchr(s, ';') : NULL; + if (s) { + v1 = api_epg_decode_channel_num(s); + if (z) + v2 = api_epg_decode_channel_num(z); + api_epg_filter_set_num(&eq.channel_num, v1, v2, comp); + } + } else { + if (!htsmsg_field_get_s64(f2, &v1)) { + if (v1 < CHANNEL_SPLIT) + v1 *= CHANNEL_SPLIT; + api_epg_filter_set_num(&eq.channel_num, v1, 0, comp); + } + } + } + } + } else if (!strcmp(k, "genre")) { + if (!strcmp(t, "numeric")) { + f2 = htsmsg_field_find(e, "value"); + if (f2) { + int64_t v; + if (f2->hmf_type == HMF_STR) { + htsmsg_t *z = api_epg_get_list(htsmsg_field_get_str(f2)); + if (z) { + htsmsg_field_t *f3; + uint32_t count = 0; + HTSMSG_FOREACH(f3, z) + count++; + if (ARRAY_SIZE(eq.genre_static) > count) + eq.genre = malloc(sizeof(eq.genre[0]) * count); + else + eq.genre = eq.genre_static; + HTSMSG_FOREACH(f3, z) + if (!htsmsg_field_get_s64(f3, &v)) + eq.genre[eq.genre_count++] = v; + } + } else { + if (!htsmsg_field_get_s64(f2, &v)) { + eq.genre_count = 1; + eq.genre = eq.genre_static; + eq.genre[0] = v; + } + } + } + } + } else if (!strcmp(t, "string")) { + if ((v = htsmsg_get_str(e, "value"))) + api_epg_filter_add_str(&eq, k, v, EC_RE); + } else if (!strcmp(t, "numeric")) { + f2 = htsmsg_field_find(e, "value"); + if (f2) { + int64_t v1, v2 = 0; + if (f2->hmf_type == HMF_STR) { + const char *z = htsmsg_field_get_str(f2); + if (z) { + const char *z2 = strchr(z, ';'); + if (z2) + v2 = strtoll(z2 + 1, NULL, 0); + } + v1 = strtoll(z, NULL, 0); + api_epg_filter_add_num(&eq, k, v1, v2, comp); + } else { + if (!htsmsg_field_get_s64(f2, &v1)) + api_epg_filter_add_num(&eq, k, v1, v2, comp); + } + } + } + } + } + + /* Sort */ + if ((str = htsmsg_get_str(args, "sort"))) { + int skey = str2val(str, sortcmptab); + if (skey >= 0) { + eq.sort_key = skey; + if ((str = htsmsg_get_str(args, "dir")) && !strcasecmp(str, "DESC")) + eq.sort_dir = IS_DSC; + else + eq.sort_dir = IS_ASC; + } + } /* else.. keep default start time ascending sorting */ /* Pagination settings */ start = htsmsg_get_u32_or_default(args, "start", 0); @@ -152,27 +420,124 @@ api_epg_grid /* Query the EPG */ pthread_mutex_lock(&global_lock); - epg_query(&eqr, ch, tag, NULL, /*genre,*/ title, lang, min_duration, max_duration); - epg_query_sort(&eqr); - // TODO: optional sorting + epg_query(&eq); /* Build response */ - start = MIN(eqr.eqr_entries, start); - end = MIN(eqr.eqr_entries, start + limit); + start = MIN(eq.entries, start); + end = MIN(eq.entries, start + limit); + l = htsmsg_create_list(); for (i = start; i < end; i++) { - if (!(e = api_epg_entry(eqr.eqr_array[i], lang))) continue; - if (!l) l = htsmsg_create_list(); + if (!(e = api_epg_entry(eq.result[i], lang))) continue; htsmsg_add_msg(l, NULL, e); } + pthread_mutex_unlock(&global_lock); + epg_query_free(&eq); + + /* Build response */ + *resp = htsmsg_create_map(); + htsmsg_add_u32(*resp, "totalCount", eq.entries); + htsmsg_add_msg(*resp, "entries", l); + + return 0; +} + +static void +api_epg_episode_broadcasts + ( htsmsg_t *l, const char *lang, epg_episode_t *ep, + uint32_t *entries, epg_broadcast_t *ebc_skip ) +{ + epg_broadcast_t *ebc; + channel_t *ch; + htsmsg_t *m; + + LIST_FOREACH(ebc, &ep->broadcasts, ep_link) { + ch = ebc->channel; + if (ch == NULL) continue; + if (ebc == ebc_skip) continue; + m = api_epg_entry(ebc, lang); + htsmsg_add_msg(l, NULL, m); + (*entries)++; + } +} + +static int +api_epg_alternative + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + uint32_t id, entries = 0; + htsmsg_t *l = htsmsg_create_list(); + epg_broadcast_t *e; + const char *lang = htsmsg_get_str(args, "lang"); + + if (!htsmsg_get_u32(args, "eventId", &id)) + return -EINVAL; + + /* Main Job */ + pthread_mutex_lock(&global_lock); + e = epg_broadcast_find_by_id(id, NULL); + if (e && e->episode) + api_epg_episode_broadcasts(l, lang, e->episode, &entries, e); pthread_mutex_unlock(&global_lock); /* Build response */ - htsmsg_add_u32(*resp, "totalCount", eqr.eqr_entries); - if (l) - htsmsg_add_msg(*resp, "events", l); + htsmsg_add_u32(*resp, "totalCount", entries); + htsmsg_add_msg(*resp, "entries", l); + return 0; +} + +static int +api_epg_related + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + uint32_t id, entries = 0; + htsmsg_t *l = htsmsg_create_list(); + epg_broadcast_t *e; + epg_episode_t *ep, *ep2; + const char *lang = htsmsg_get_str(args, "lang"); + if (!htsmsg_get_u32(args, "eventId", &id)) + return -EINVAL; + + /* Main Job */ + pthread_mutex_lock(&global_lock); + e = epg_broadcast_find_by_id(id, NULL); + ep = e ? e->episode : NULL; + if (ep && ep->brand) { + LIST_FOREACH(ep2, &ep->brand->episodes, blink) { + if (ep2 == ep) continue; + if (!ep2->title) continue; + api_epg_episode_broadcasts(l, lang, ep2, &entries, e); + entries++; + } + } else if (ep && ep->season) { + LIST_FOREACH(ep2, &ep->season->episodes, slink) { + if (ep2 == ep) continue; + if (!ep2->title) continue; + api_epg_episode_broadcasts(l, lang, ep2, &entries, e); + } + } + pthread_mutex_unlock(&global_lock); + + /* Build response */ + htsmsg_add_u32(*resp, "totalCount", entries); + htsmsg_add_msg(*resp, "entries", l); + + return 0; +} + +static int +api_epg_brand_list(access_t *perm, void *opaque, const char *op, + htsmsg_t *args, htsmsg_t **resp) +{ + htsmsg_t *array; + + *resp = htsmsg_create_map(); + pthread_mutex_lock(&global_lock); + array = epg_brand_list(); + pthread_mutex_unlock(&global_lock); + htsmsg_add_msg(*resp, "entries", array); return 0; } @@ -181,19 +546,24 @@ api_epg_content_type_list(access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp) { htsmsg_t *array; + int full = 0; + + htsmsg_get_bool(args, "full", &full); *resp = htsmsg_create_map(); - array = epg_genres_list_all(1, 0); + array = epg_genres_list_all(full ? 0 : 1, 0); htsmsg_add_msg(*resp, "entries", array); return 0; } - void api_epg_init ( void ) { static api_hook_t ah[] = { - { "epg/data/grid", ACCESS_ANONYMOUS, api_epg_grid, NULL }, - { "epg/content_type/list", ACCESS_ANONYMOUS, api_epg_content_type_list, NULL }, + { "epg/events/grid", ACCESS_ANONYMOUS, api_epg_grid, NULL }, + { "epg/events/alternative", ACCESS_ANONYMOUS, api_epg_alternative, NULL }, + { "epg/events/related", ACCESS_ANONYMOUS, api_epg_related, NULL }, + { "epg/brand/list", ACCESS_ANONYMOUS, api_epg_brand_list, NULL }, + { "epg/content_type/list", ACCESS_ANONYMOUS, api_epg_content_type_list, NULL }, { NULL }, }; diff --git a/src/api/api_idnode.c b/src/api/api_idnode.c index d3330f9c..abfed290 100644 --- a/src/api/api_idnode.c +++ b/src/api/api_idnode.c @@ -101,7 +101,7 @@ api_idnode_grid_conf /* Sort */ if ((str = htsmsg_get_str(args, "sort"))) { conf->sort.key = str; - if ((str = htsmsg_get_str(args, "dir")) && !strcmp(str, "DESC")) + if ((str = htsmsg_get_str(args, "dir")) && !strcasecmp(str, "DESC")) conf->sort.dir = IS_DSC; else conf->sort.dir = IS_ASC; diff --git a/src/channels.c b/src/channels.c index 9f23c356..ad37629b 100644 --- a/src/channels.c +++ b/src/channels.c @@ -386,6 +386,8 @@ channel_t * channel_find_by_name ( const char *name ) { channel_t *ch; + if (name == NULL) + return NULL; CHANNEL_FOREACH(ch) if (!strcmp(channel_get_name(ch), name)) break; @@ -931,6 +933,9 @@ channel_tag_find_by_name(const char *name, int create) { channel_tag_t *ct; + if (name == NULL) + return NULL; + TAILQ_FOREACH(ct, &channel_tags, ct_link) if(!strcasecmp(ct->ct_name, name)) return ct; diff --git a/src/epg.c b/src/epg.c index aa9ce659..19d6ca54 100644 --- a/src/epg.c +++ b/src/epg.c @@ -1752,6 +1752,12 @@ const char *epg_broadcast_get_title ( epg_broadcast_t *b, const char *lang ) return epg_episode_get_title(b->episode, lang); } +const char *epg_broadcast_get_subtitle ( epg_broadcast_t *b, const char *lang ) +{ + if (!b || !b->episode) return NULL; + return epg_episode_get_subtitle(b->episode, lang); +} + const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang ) { if (!b || !b->summary) return NULL; @@ -2190,7 +2196,7 @@ htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix ) for (j = 0; j < (major_only ? 1 : 16); j++) { if (_epg_genre_names[i][j]) { e = htsmsg_create_map(); - htsmsg_add_u32(e, "key", major_only ? i : (i << 4 | j)); + htsmsg_add_u32(e, "key", (i << 4) | (major_only ? 0 : j)); htsmsg_add_str(e, "val", _epg_genre_names[i][j]); // TODO: use major_prefix htsmsg_add_msg(m, NULL, e); @@ -2204,109 +2210,393 @@ htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix ) * Querying * *************************************************************************/ -static void _eqr_add - ( epg_query_result_t *eqr, epg_broadcast_t *e, - epg_genre_t *genre, regex_t *preg, time_t start, const char *lang, int min_duration, int max_duration ) +static inline int +_eq_comp_num ( epg_filter_num_t *f, int64_t val ) { - const char *title; - double duration; - - /* Ignore */ - if ( e->stop < start ) return; - if ( !(title = epg_episode_get_title(e->episode, lang)) ) return; - if ( genre && !epg_genre_list_contains(&e->episode->genre, genre, 1) ) return; - if ( preg && regexec(preg, title, 0, NULL, 0)) return; - - duration = difftime(e->stop,e->start); - if ( duration < min_duration || duration > max_duration ) return; - - /* More space */ - if ( eqr->eqr_entries == eqr->eqr_alloced ) { - eqr->eqr_alloced = MAX(100, eqr->eqr_alloced * 2); - eqr->eqr_array = realloc(eqr->eqr_array, - eqr->eqr_alloced * sizeof(epg_broadcast_t)); + switch (f->comp) { + case EC_EQ: return val != f->val1; + case EC_LT: return val > f->val1; + case EC_GT: return val < f->val1; + case EC_RG: return val < f->val1 || val > f->val2; + default: return 0; } - - /* Store */ - eqr->eqr_array[eqr->eqr_entries++] = e; } -static void _eqr_add_channel - ( epg_query_result_t *eqr, channel_t *ch, epg_genre_t *genre, - regex_t *preg, time_t start, const char *lang, int min_duration, int max_duration ) +static inline int +_eq_comp_str ( epg_filter_str_t *f, const char *str ) +{ + switch (f->comp) { + case EC_EQ: return strcmp(str, f->str); + case EC_LT: return strcmp(str, f->str) > 0; + case EC_GT: return strcmp(str, f->str) < 0; + case EC_IN: return strstr(str, f->str) != NULL; + case EC_RE: return regexec(&f->re, str, 0, NULL, 0) != 0; + default: return 0; + } +} + +static void +_eq_add ( epg_query_t *eq, epg_broadcast_t *e ) +{ + const char *s, *lang = eq->lang; + epg_episode_t *ep; + + /* Filtering */ + if (e->stop < dispatch_clock) return; + if (_eq_comp_num(&eq->start, e->start)) return; + if (_eq_comp_num(&eq->stop, e->stop)) return; + if (eq->duration.comp != EC_NO) { + int64_t duration = (int64_t)e->stop - (int64_t)e->start; + if (_eq_comp_num(&eq->duration, duration)) return; + } + ep = e->episode; + if (eq->stars.comp != EC_NO) { + if (e == NULL) return; + if (_eq_comp_num(&eq->stars, ep->star_rating)) return; + } + if (eq->age.comp != EC_NO) { + if (e == NULL) return; + if (_eq_comp_num(&eq->age, ep->age_rating)) return; + } + if (eq->channel_num.comp != EC_NO) + if (_eq_comp_num(&eq->channel_num, channel_get_number(e->channel))) return; + if (eq->channel_name.comp != EC_NO) + if (_eq_comp_str(&eq->channel_name, channel_get_name(e->channel))) return; + if (eq->genre_count) { + epg_genre_t genre; + uint32_t i, r = 0; + for (i = 0; i < eq->genre_count; i++) { + genre.code = eq->genre[i]; + if (genre.code == 0) continue; + if (epg_genre_list_contains(&e->episode->genre, &genre, 1)) r++; + } + if (!r) return; + } + if (eq->title.comp != EC_NO || eq->stitle) { + if ((s = epg_episode_get_title(ep, lang)) == NULL) return; + if (eq->stitle) + if (regexec(&eq->stitle_re, s, 0, NULL, 0)) return; + if (_eq_comp_str(&eq->title, s)) return; + } + if (eq->subtitle.comp != EC_NO) { + if ((s = epg_episode_get_subtitle(ep, lang)) == NULL) return; + if (_eq_comp_str(&eq->subtitle, s)) return; + } + if (eq->summary.comp != EC_NO) { + if ((s = epg_episode_get_summary(ep, lang)) == NULL) return; + if (_eq_comp_str(&eq->summary, s)) return; + } + if (eq->description.comp != EC_NO) { + if ((s = epg_episode_get_description(ep, lang)) == NULL) return; + if (_eq_comp_str(&eq->description, s)) return; + } + + /* More space */ + if (eq->entries == eq->allocated) { + eq->allocated = MAX(100, eq->allocated + 100); + eq->result = realloc(eq->result, eq->allocated * sizeof(epg_broadcast_t *)); + } + + /* Store */ + eq->result[eq->entries++] = e; +} + +static void +_eq_add_channel ( epg_query_t *eq, channel_t *ch ) { epg_broadcast_t *ebc; RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) { - if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start, lang, min_duration, max_duration); + if (ebc->episode) + _eq_add(eq, ebc); } } -void epg_query0 - ( epg_query_result_t *eqr, channel_t *channel, channel_tag_t *tag, - epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration ) +static int +_eq_init_str( epg_filter_str_t *f ) { - time_t now; - channel_tag_mapping_t *ctm; - regex_t preg0, *preg; - time(&now); + if (f->comp != EC_RE) return 0; + return regcomp(&f->re, f->str, REG_ICASE | REG_EXTENDED | REG_NOSUB); +} - /* Clear (just incase) */ - memset(eqr, 0, sizeof(epg_query_result_t)); +static void +_eq_done_str( epg_filter_str_t *f ) +{ + if (f->comp == EC_RE) + regfree(&f->re); + free(f->str); + f->str = NULL; +} + +static int _epg_sort_start_ascending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)a)->start - (*(epg_broadcast_t**)b)->start; +} + +static int _epg_sort_start_descending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)b)->start - (*(epg_broadcast_t**)a)->start; +} + +static int _epg_sort_stop_ascending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)a)->stop - (*(epg_broadcast_t**)b)->stop; +} + +static int _epg_sort_stop_descending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)b)->stop - (*(epg_broadcast_t**)a)->stop; +} + +static inline int64_t _epg_sort_duration( const epg_broadcast_t *b ) +{ + return b->stop - b->start; +} + +static int _epg_sort_duration_ascending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_duration(*(epg_broadcast_t**)a) - _epg_sort_duration(*(epg_broadcast_t**)b); +} + +static int _epg_sort_duration_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_duration(*(epg_broadcast_t**)b) - _epg_sort_duration(*(epg_broadcast_t**)a); +} + +static int _epg_sort_title_ascending ( const void *a, const void *b, void *eq ) +{ + const char *s1 = epg_broadcast_get_title(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang); + const char *s2 = epg_broadcast_get_title(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang); + if (s1 == NULL && s2) return 1; + if (s1 && s2 == NULL) return -1; + return strcmp(s1, s2); +} + +static int _epg_sort_title_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_title_ascending(a, b, eq) * -1; +} + +static int _epg_sort_subtitle_ascending ( const void *a, const void *b, void *eq ) +{ + const char *s1 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang); + const char *s2 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang); + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2) return 1; + if (s1 && s2 == NULL) return -1; + return strcmp(s1, s2); +} + +static int _epg_sort_subtitle_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_subtitle_ascending(a, b, eq) * -1; +} + +static int _epg_sort_summary_ascending ( const void *a, const void *b, void *eq ) +{ + const char *s1 = epg_broadcast_get_summary(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang); + const char *s2 = epg_broadcast_get_summary(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang); + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2) return 1; + if (s1 && s2 == NULL) return -1; + return strcmp(s1, s2); +} + +static int _epg_sort_summary_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_summary_ascending(a, b, eq) * -1; +} + +static int _epg_sort_description_ascending ( const void *a, const void *b, void *eq ) +{ + const char *s1 = epg_broadcast_get_description(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang); + const char *s2 = epg_broadcast_get_description(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang); + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL && s2) return 1; + if (s1 && s2 == NULL) return -1; + return strcmp(s1, s2); +} + +static int _epg_sort_description_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_description_ascending(a, b, eq) * -1; +} + +static int _epg_sort_channel_ascending ( const void *a, const void *b, void *eq ) +{ + char *s1 = strdup(channel_get_name((*(epg_broadcast_t**)a)->channel)); + char *s2 = strdup(channel_get_name((*(epg_broadcast_t**)b)->channel)); + int r = strcmp(s1, s2); + free(s2); + free(s1); + return r; +} + +static int _epg_sort_channel_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_description_ascending(a, b, eq) * -1; +} + +static int _epg_sort_channel_num_ascending ( const void *a, const void *b, void *eq ) +{ + int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel); + int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel); + return v1 - v2; +} + +static int _epg_sort_channel_num_descending ( const void *a, const void *b, void *eq ) +{ + int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel); + int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel); + return v2 - v1; +} + +static int _epg_sort_stars_ascending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)a)->episode->star_rating - (*(epg_broadcast_t**)b)->episode->star_rating; +} + +static int _epg_sort_stars_descending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)b)->episode->star_rating - (*(epg_broadcast_t**)a)->episode->star_rating; +} + +static int _epg_sort_age_ascending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)a)->episode->age_rating - (*(epg_broadcast_t**)b)->episode->age_rating; +} + +static int _epg_sort_age_descending ( const void *a, const void *b, void *eq ) +{ + return (*(epg_broadcast_t**)b)->episode->age_rating - (*(epg_broadcast_t**)a)->episode->age_rating; +} + +static uint64_t _epg_sort_genre_hash( epg_episode_t *ep ) +{ + uint64_t h = 0, t; + epg_genre_t *g; + + LIST_FOREACH(g, &ep->genre, link) { + t = h >> 28; + h <<= 8; + h += (uint64_t)g->code + t; + } + return h; +} + +static int _epg_sort_genre_ascending ( const void *a, const void *b, void *eq ) +{ + uint64_t v1 = _epg_sort_genre_hash((*(epg_broadcast_t**)a)->episode); + uint64_t v2 = _epg_sort_genre_hash((*(epg_broadcast_t**)b)->episode); + return v1 - v2; +} + +static int _epg_sort_genre_descending ( const void *a, const void *b, void *eq ) +{ + return _epg_sort_genre_ascending(a, b, eq) * -1; +} + +epg_broadcast_t ** +epg_query ( epg_query_t *eq ) +{ + channel_t *channel; + channel_tag_t *tag; + int (*fcn)(const void *, const void *, void *) = NULL; /* Setup exp */ - if ( title ) { - if (regcomp(&preg0, title, REG_ICASE | REG_EXTENDED | REG_NOSUB) ) - return; - preg = &preg0; - } else { - preg = NULL; - } - + if (_eq_init_str(&eq->title)) goto fin; + if (_eq_init_str(&eq->subtitle)) goto fin; + if (_eq_init_str(&eq->summary)) goto fin; + if (_eq_init_str(&eq->description)) goto fin; + if (_eq_init_str(&eq->channel_name)) goto fin; + + if (eq->stitle) + if (regcomp(&eq->stitle_re, eq->stitle, REG_ICASE | REG_EXTENDED | REG_NOSUB)) + goto fin; + + channel = channel_find_by_uuid(eq->channel) ?: + channel_find_by_name(eq->channel); + + tag = channel_tag_find_by_uuid(eq->channel_tag) ?: + channel_tag_find_by_name(eq->channel_tag, 0); + /* Single channel */ - if (channel && !tag) { - _eqr_add_channel(eqr, channel, genre, preg, now, lang, min_duration, max_duration); + if (channel && tag == NULL) { + _eq_add_channel(eq, channel); /* Tag based */ - } else if ( tag ) { + } else if (tag) { + channel_tag_mapping_t *ctm; LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) { if(channel == NULL || ctm->ctm_channel == channel) - _eqr_add_channel(eqr, ctm->ctm_channel, genre, preg, now, lang, min_duration, max_duration); + _eq_add_channel(eq, ctm->ctm_channel); } /* All channels */ } else { CHANNEL_FOREACH(channel) - _eqr_add_channel(eqr, channel, genre, preg, now, lang, min_duration, max_duration); + _eq_add_channel(eq, channel); } - if (preg) regfree(preg); - return; + switch (eq->sort_dir) { + case ES_ASC: + switch (eq->sort_key) { + case ESK_START: fcn = _epg_sort_start_ascending; break; + case ESK_STOP: fcn = _epg_sort_stop_ascending; break; + case ESK_DURATION: fcn = _epg_sort_duration_ascending; break; + case ESK_TITLE: fcn = _epg_sort_title_ascending; break; + case ESK_SUBTITLE: fcn = _epg_sort_subtitle_ascending; break; + case ESK_SUMMARY: fcn = _epg_sort_summary_ascending; break; + case ESK_DESCRIPTION: fcn = _epg_sort_description_ascending; break; + case ESK_CHANNEL: fcn = _epg_sort_channel_ascending; break; + case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_ascending; break; + case ESK_STARS: fcn = _epg_sort_stars_ascending; break; + case ESK_AGE: fcn = _epg_sort_age_ascending; break; + case ESK_GENRE: fcn = _epg_sort_genre_ascending; break; + } + break; + case ES_DSC: + switch (eq->sort_key) { + case ESK_START: fcn = _epg_sort_start_descending; break; + case ESK_STOP: fcn = _epg_sort_stop_descending; break; + case ESK_DURATION: fcn = _epg_sort_duration_descending; break; + case ESK_TITLE: fcn = _epg_sort_title_descending; break; + case ESK_SUBTITLE: fcn = _epg_sort_subtitle_descending; break; + case ESK_SUMMARY: fcn = _epg_sort_summary_descending; break; + case ESK_DESCRIPTION: fcn = _epg_sort_description_descending; break; + case ESK_CHANNEL: fcn = _epg_sort_channel_descending; break; + case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_descending; break; + case ESK_STARS: fcn = _epg_sort_stars_descending; break; + case ESK_AGE: fcn = _epg_sort_age_descending; break; + case ESK_GENRE: fcn = _epg_sort_genre_descending; break; + } + break; + } + + tvh_qsort_r(eq->result, eq->entries, sizeof(epg_broadcast_t *), fcn, eq); + +fin: + _eq_done_str(&eq->title); + _eq_done_str(&eq->subtitle); + _eq_done_str(&eq->summary); + _eq_done_str(&eq->description); + _eq_done_str(&eq->channel_name); + + free(eq->lang); eq->lang = NULL; + free(eq->channel); eq->channel = NULL; + free(eq->channel_tag); eq->channel_tag = NULL; + free(eq->stitle); eq->stitle = NULL; + if (eq->genre != eq->genre_static) + free(eq->genre); + eq->genre = NULL; + + return eq->result; } -void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag, - epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration) +void epg_query_free(epg_query_t *eq) { - channel_t *ch = channel ? channel_find(channel) : NULL; - channel_tag_t *ct = tag ? channel_tag_find_by_uuid(tag) : NULL; - - epg_query0(eqr, ch, ct, genre, title, lang, min_duration, max_duration); + free(eq->result); eq->result = NULL; } -void epg_query_free(epg_query_result_t *eqr) -{ - free(eqr->eqr_array); -} - -static int _epg_sort_start_ascending ( const void *a, const void *b ) -{ - return (*(epg_broadcast_t**)a)->start - (*(epg_broadcast_t**)b)->start; -} - -void epg_query_sort(epg_query_result_t *eqr) -{ - qsort(eqr->eqr_array, eqr->eqr_entries, sizeof(epg_broadcast_t*), - _epg_sort_start_ascending); -} /* ************************************************************************** * Miscellaneous diff --git a/src/epg.h b/src/epg.h index eb3fcb87..fd17e5da 100644 --- a/src/epg.h +++ b/src/epg.h @@ -19,6 +19,7 @@ #ifndef EPG_H #define EPG_H +#include #include "settings.h" #include "lang_str.h" @@ -507,6 +508,8 @@ epg_episode_t *epg_broadcast_get_episode ( epg_broadcast_t *b, int create, int *save ); const char *epg_broadcast_get_title ( epg_broadcast_t *b, const char *lang ); +const char *epg_broadcast_get_subtitle + ( epg_broadcast_t *b, const char *lang ); const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang ); const char *epg_broadcast_get_description @@ -528,28 +531,80 @@ void epg_channel_unlink ( struct channel *ch ); * Querying * ***********************************************************************/ -/* - * Query result - */ -typedef struct epg_query_result { - epg_broadcast_t **eqr_array; - int eqr_entries; - int eqr_alloced; -} epg_query_result_t; +typedef enum { + EC_NO, ///< No filter + EC_EQ, ///< Equals + EC_LT, ///< LT + EC_GT, ///< GT + EC_RG, ///< Range + EC_IN, ///< contains (STR only) + EC_RE, ///< regexp (STR only) +} epg_comp_t; -void epg_query_free(epg_query_result_t *eqr); +typedef struct epg_filter_str { + char *str; + regex_t re; + epg_comp_t comp; +} epg_filter_str_t; -/* Sorting */ -// WIBNI: might be useful to have a user defined comparator function -void epg_query_sort(epg_query_result_t *eqr); +typedef struct epg_filter_num { + int64_t val1; + int64_t val2; + epg_comp_t comp; +} epg_filter_num_t; -/* Query routines */ -void epg_query0(epg_query_result_t *eqr, struct channel *ch, - struct channel_tag *ct, epg_genre_t *genre, const char *title, - const char *lang, int min_duration, int max_duration); -void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag, - epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration); +typedef struct epg_query { + /* Configuration */ + char *lang; + /* Filter */ + epg_filter_num_t start; + epg_filter_num_t stop; + epg_filter_num_t duration; + epg_filter_str_t title; + epg_filter_str_t subtitle; + epg_filter_str_t summary; + epg_filter_str_t description; + epg_filter_num_t episode; + epg_filter_num_t stars; + epg_filter_num_t age; + epg_filter_str_t channel_name; + epg_filter_num_t channel_num; + char *stitle; + regex_t stitle_re; + char *channel; + char *channel_tag; + uint32_t genre_count; + uint8_t *genre; + uint8_t genre_static[16]; + + enum { + ESK_START, + ESK_STOP, + ESK_DURATION, + ESK_TITLE, + ESK_SUBTITLE, + ESK_SUMMARY, + ESK_DESCRIPTION, + ESK_CHANNEL, + ESK_CHANNEL_NUM, + ESK_STARS, + ESK_AGE, + ESK_GENRE + } sort_key; + enum { + ES_ASC, + ES_DSC + } sort_dir; + + /* Result */ + epg_broadcast_t **result; + uint32_t entries; + uint32_t allocated; +} epg_query_t; + +epg_broadcast_t **epg_query(epg_query_t *eq); +void epg_query_free(epg_query_t *eq); /* ************************************************************************ * Setup/Shutdown diff --git a/src/htsp_server.c b/src/htsp_server.c index b7d44bfd..c78b6ab2 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -1142,8 +1142,7 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in) uint32_t u32, full; channel_t *ch = NULL; channel_tag_t *ct = NULL; - epg_query_result_t eqr; - epg_genre_t genre, *eg = NULL; + epg_query_t eq; const char *lang; int min_duration; int max_duration; @@ -1151,48 +1150,61 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in) /* Required */ if( (query = htsmsg_get_str(in, "query")) == NULL ) return htsp_error("Missing argument 'query'"); + + memset(&eq, 0, sizeof(eq)); /* Optional */ - if(!(htsmsg_get_u32(in, "channelId", &u32))) + if(!(htsmsg_get_u32(in, "channelId", &u32))) { if (!(ch = channel_find_by_id(u32))) return htsp_error("Channel does not exist"); - if(!(htsmsg_get_u32(in, "tagId", &u32))) + else + eq.channel = strdup(idnode_uuid_as_str(&ch->ch_id)); + } + if(!(htsmsg_get_u32(in, "tagId", &u32))) { if (!(ct = htsp_channel_tag_find_by_identifier(u32))) return htsp_error("Channel tag does not exist"); + else + eq.channel_tag = strdup(idnode_uuid_as_str(&ct->ct_id)); + } if (!htsmsg_get_u32(in, "contentType", &u32)) { if(htsp->htsp_version < 6) u32 <<= 4; - genre.code = u32; - eg = &genre; + eq.genre_count = 1; + eq.genre = eq.genre_static; + eq.genre[0] = u32; } lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language; + eq.lang = lang ? strdup(lang) : NULL; full = htsmsg_get_u32_or_default(in, "full", 0); min_duration = htsmsg_get_u32_or_default(in, "minduration", 0); max_duration = htsmsg_get_u32_or_default(in, "maxduration", INT_MAX); + eq.duration.comp = EC_RG; + eq.duration.val1 = min_duration; + eq.duration.val2 = max_duration; tvhtrace("htsp", "min_duration %d and max_duration %d", min_duration, max_duration); /* Check access */ if (!htsp_user_access_channel(htsp, ch)) return htsp_error("User does not have access"); - //do the query - epg_query0(&eqr, ch, ct, eg, query, lang, min_duration, max_duration); + /* Query */ + epg_query(&eq); - // create reply + /* Create Reply */ out = htsmsg_create_map(); - if( eqr.eqr_entries ) { + if( eq.entries ) { array = htsmsg_create_list(); - for(i = 0; i < eqr.eqr_entries; ++i) { + for(i = 0; i < eq.entries; ++i) { if (full) htsmsg_add_msg(array, NULL, - htsp_build_event(eqr.eqr_array[i], NULL, lang, 0, htsp)); + htsp_build_event(eq.result[i], NULL, lang, 0, htsp)); else - htsmsg_add_u32(array, NULL, eqr.eqr_array[i]->id); + htsmsg_add_u32(array, NULL, eq.result[i]->id); } htsmsg_add_msg(out, full ? "events" : "eventIds", array); } - epg_query_free(&eqr); + epg_query_free(&eq); return out; } diff --git a/src/webui/extjs.c b/src/webui/extjs.c index edadc6e6..521102fa 100755 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -540,253 +540,6 @@ extjs_languages(http_connection_t *hc, const char *remain, void *opaque) } -/** - * - */ -static int -extjs_epg(http_connection_t *hc, const char *remain, void *opaque) -{ - htsbuf_queue_t *hq = &hc->hc_reply; - htsmsg_t *out, *array, *m; - epg_query_result_t eqr; - epg_broadcast_t *e; - epg_episode_t *ee = NULL; - epg_genre_t *eg = NULL, genre; - channel_t *ch; - int start = 0, end, limit, i; - const char *s; - char buf[100]; - const char *channel = http_arg_get(&hc->hc_req_args, "channel"); - const char *tag = http_arg_get(&hc->hc_req_args, "tag"); - const char *title = http_arg_get(&hc->hc_req_args, "title"); - const char *lang = http_arg_get(&hc->hc_args, "Accept-Language"); - - int min_duration; - int max_duration; - - if(channel && !channel[0]) channel = NULL; - if(tag && !tag[0]) tag = NULL; - - if((s = http_arg_get(&hc->hc_req_args, "minduration")) != NULL) - min_duration = atoi(s); - else - min_duration = 0; - - if((s = http_arg_get(&hc->hc_req_args, "maxduration")) != NULL) - max_duration = atoi(s); - else - max_duration = INT_MAX; - - if((s = http_arg_get(&hc->hc_req_args, "start")) != NULL) - start = atoi(s); - - if((s = http_arg_get(&hc->hc_req_args, "limit")) != NULL) - limit = atoi(s); - else - limit = 20; /* XXX */ - - if ((s = http_arg_get(&hc->hc_req_args, "content_type"))) { - genre.code = atoi(s) * 16; - eg = &genre; - } - - out = htsmsg_create_map(); - array = htsmsg_create_list(); - - pthread_mutex_lock(&global_lock); - - epg_query(&eqr, channel, tag, eg, title, lang, min_duration, max_duration); - - epg_query_sort(&eqr); - - htsmsg_add_u32(out, "totalCount", eqr.eqr_entries); - - - start = MIN(start, eqr.eqr_entries); - end = MIN(start + limit, eqr.eqr_entries); - - for(i = start; i < end; i++) { - e = eqr.eqr_array[i]; - ee = e->episode; - ch = e->channel; - if (!ch||!ee) continue; - - m = htsmsg_create_map(); - - htsmsg_add_str(m, "channel", channel_get_name(ch)); - htsmsg_add_u32(m, "channelid", channel_get_id(ch)); - if(ch->ch_icon != NULL) - htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); - - if((s = epg_episode_get_title(ee, lang))) - htsmsg_add_str(m, "title", s); - if((s = epg_episode_get_subtitle(ee, lang))) - htsmsg_add_str(m, "subtitle", s); - - if((s = epg_broadcast_get_description(e, lang))) - htsmsg_add_str(m, "description", s); - else if((s = epg_broadcast_get_summary(e, lang))) - htsmsg_add_str(m, "description", s); - - if (epg_episode_number_format(ee, buf, 100, NULL, "Season %d", ".", - "Episode %d", "/%d")) - htsmsg_add_str(m, "episode", buf); - - htsmsg_add_u32(m, "id", e->id); - htsmsg_add_u32(m, "start", e->start); - htsmsg_add_u32(m, "end", e->stop); - htsmsg_add_u32(m, "duration", e->stop - e->start); - if(ee->star_rating) - htsmsg_add_u32(m, "starrating", ee->star_rating); - if(ee->age_rating) - htsmsg_add_u32(m, "agerating", ee->age_rating); - - if(e->serieslink) - htsmsg_add_str(m, "serieslink", e->serieslink->uri); - - if((eg = LIST_FIRST(&ee->genre))) { - htsmsg_add_u32(m, "content_type", eg->code / 16); - } - - dvr_entry_t *de; - if((de = dvr_entry_find_by_event(e)) != NULL) - htsmsg_add_str(m, "schedstate", dvr_entry_schedstatus(de)); - - htsmsg_add_msg(array, NULL, m); - } - - epg_query_free(&eqr); - - pthread_mutex_unlock(&global_lock); - - htsmsg_add_msg(out, "entries", array); - - htsmsg_json_serialize(out, hq, 0); - htsmsg_destroy(out); - http_output_content(hc, "text/x-json; charset=UTF-8"); - return 0; -} - -static int -extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque) -{ - htsbuf_queue_t *hq = &hc->hc_reply; - htsmsg_t *out, *array, *m; - epg_broadcast_t *e, *ebc; - epg_episode_t *ee, *ee2; - channel_t *ch; - uint32_t count = 0; - const char *s; - char buf[100]; - - const char *lang = http_arg_get(&hc->hc_args, "Accept-Language"); - const char *id = http_arg_get(&hc->hc_req_args, "id"); - const char *type = http_arg_get(&hc->hc_req_args, "type"); - - out = htsmsg_create_map(); - array = htsmsg_create_list(); - - pthread_mutex_lock(&global_lock); - if ( id && type ) { - e = epg_broadcast_find_by_id(atoi(id), NULL); - if ( e && e->episode ) { - ee = e->episode; - - /* Alternative broadcasts */ - if (!strcmp(type, "alternative")) { - LIST_FOREACH(ebc, &ee->broadcasts, ep_link) { - ch = ebc->channel; - if ( !ch ) continue; // skip something not viewable - if ( ebc == e ) continue; // skip self - count++; - m = htsmsg_create_map(); - htsmsg_add_u32(m, "id", ebc->id); - htsmsg_add_str(m, "channel", channel_get_name(ch)); - if (ch->ch_icon) - htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); - htsmsg_add_u32(m, "start", ebc->start); - htsmsg_add_msg(array, NULL, m); - } - - /* Related */ - } else if (!strcmp(type, "related")) { - if (ee->brand) { - LIST_FOREACH(ee2, &ee->brand->episodes, blink) { - if (ee2 == ee) continue; - if (!ee2->title) continue; - count++; - m = htsmsg_create_map(); - htsmsg_add_str(m, "uri", ee2->uri); - if ((s = epg_episode_get_title(ee2, lang))) - htsmsg_add_str(m, "title", s); - if ((s = epg_episode_get_subtitle(ee2, lang))) - htsmsg_add_str(m, "subtitle", s); - if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d", - ".", "Episode %d", "/%d")) - htsmsg_add_str(m, "episode", buf); - htsmsg_add_msg(array, NULL, m); - } - } else if (ee->season) { - LIST_FOREACH(ee2, &ee->season->episodes, slink) { - if (ee2 == ee) continue; - if (!ee2->title) continue; - count++; - m = htsmsg_create_map(); - htsmsg_add_str(m, "uri", ee2->uri); - if ((s = epg_episode_get_title(ee2, lang))) - htsmsg_add_str(m, "title", s); - if ((s = epg_episode_get_subtitle(ee2, lang))) - htsmsg_add_str(m, "subtitle", s); - if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d", - ".", "Episode %d", "/%d")) - htsmsg_add_str(m, "episode", buf); - htsmsg_add_msg(array, NULL, m); - } - } - } - } - } - pthread_mutex_unlock(&global_lock); - - htsmsg_add_u32(out, "totalCount", count); - htsmsg_add_msg(out, "entries", array); - htsmsg_json_serialize(out, hq, 0); - htsmsg_destroy(out); - http_output_content(hc, "text/x-json; charset=UTF-8"); - return 0; -} - -/** - * - */ -static int -extjs_epgobject(http_connection_t *hc, const char *remain, void *opaque) -{ - htsbuf_queue_t *hq = &hc->hc_reply; - const char *op = http_arg_get(&hc->hc_req_args, "op"); - htsmsg_t *out, *array; - - if(op == NULL) - return 400; - - if (!strcmp(op, "brandList")) { - out = htsmsg_create_map(); - pthread_mutex_lock(&global_lock); - array = epg_brand_list(); - pthread_mutex_unlock(&global_lock); - htsmsg_add_msg(out, "entries", array); - - } else { - return HTTP_STATUS_BAD_REQUEST; - } - - htsmsg_json_serialize(out, hq, 0); - htsmsg_destroy(out); - http_output_content(hc, "text/x-json; charset=UTF-8"); - - return 0; -} - /** * */ @@ -1062,9 +815,6 @@ extjs_start(void) http_path_add("/capabilities", NULL, extjs_capabilities, ACCESS_WEB_INTERFACE); http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE); http_path_add("/epggrab", NULL, extjs_epggrab, ACCESS_WEB_INTERFACE); - http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE); - http_path_add("/epgrelated", NULL, extjs_epgrelated, ACCESS_WEB_INTERFACE); - http_path_add("/epgobject", NULL, extjs_epgobject, ACCESS_WEB_INTERFACE); http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE); http_path_add("/languages", NULL, extjs_languages, ACCESS_WEB_INTERFACE); #if ENABLE_TIMESHIFT diff --git a/src/webui/simpleui.c b/src/webui/simpleui.c index bb0ad8f6..1bf03663 100644 --- a/src/webui/simpleui.c +++ b/src/webui/simpleui.c @@ -164,7 +164,6 @@ page_simple(http_connection_t *hc, dvr_entry_t *de; dvr_query_result_t dqr; const char *rstatus = NULL; - epg_query_result_t eqr; const char *lang = http_arg_get(&hc->hc_args, "Accept-Language"); htsbuf_qprintf(hq, ""); @@ -184,14 +183,17 @@ page_simple(http_connection_t *hc, if(s != NULL) { + epg_query_t eq; + + memset(&eq, 0, sizeof(eq)); + eq.lang = strdup(lang); //Note: force min/max durations for this interface to 0 and INT_MAX seconds respectively - epg_query(&eqr, NULL, NULL, NULL, s, lang, 0, INT_MAX); - epg_query_sort(&eqr); + epg_query(&eq); - c = eqr.eqr_entries; + c = eq.entries; - if(eqr.eqr_entries == 0) { + if(eq.entries == 0) { htsbuf_qprintf(hq, "No matching entries found"); } else { @@ -206,7 +208,7 @@ page_simple(http_connection_t *hc, memset(&day, -1, sizeof(struct tm)); for(k = 0; k < c; k++) { - e = eqr.eqr_array[k]; + e = eq.result[k]; localtime_r(&e->start, &a); localtime_r(&e->stop, &b); @@ -234,7 +236,7 @@ page_simple(http_connection_t *hc, } } htsbuf_qprintf(hq, "
"); - epg_query_free(&eqr); + epg_query_free(&eq); } diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js index d0ed1546..3118dfeb 100644 --- a/src/webui/static/app/epg.js +++ b/src/webui/static/app/epg.js @@ -1,13 +1,3 @@ -tvheadend.brands = new Ext.data.JsonStore({ - root: 'entries', - fields: ['uri', 'title'], - autoLoad: true, - url: 'epgobject', - baseParams: { - op: 'brandList' - } -}); - insertContentGroupClearOption = function( scope, records, options ){ var placeholder = Ext.data.Record.create(['val', 'key']); scope.insert(0,new placeholder({val: '(Clear filter)', key: '-1'})); @@ -22,7 +12,23 @@ tvheadend.ContentGroupStore = tvheadend.idnode_get_enum({ tvheadend.contentGroupLookupName = function(code) { ret = ""; + if (!code) + code = 0; tvheadend.ContentGroupStore.each(function(r) { + if (r.data.key === code & 0xf0) + ret = r.data.val; + }); + return ret; +}; + +tvheadend.ContentGroupFullStore = tvheadend.idnode_get_enum({ + url: 'api/epg/content_type/list', + params: { full: 1 } +}); + +tvheadend.contentGroupFullLookupName = function(code) { + ret = ""; + tvheadend.ContentGroupFullStore.each(function(r) { if (r.data.key === code) ret = r.data.val; }); @@ -40,7 +46,7 @@ tvheadend.channelLookupName = function(key) { return channelString; }; -tvheadend.tagLookupName = function(key) { +tvheadend.channelTagLookupName = function(key) { tagString = ""; var index = tvheadend.channelTags.find('key', key); @@ -83,38 +89,46 @@ tvheadend.epgDetails = function(event) { var content = ''; - if (event.chicon != null && event.chicon.length > 0) - content += ''; + if (event.channelIcon != null && event.channelIcon.length > 0) + content += ''; content += '
' + event.title; if (event.subtitle) content += " : " + event.subtitle; content += '
'; - content += '
' + event.episode + '
'; - content += '
' + event.description + '
'; - content += '
' + event.starrating + '
'; - content += '
' + event.agerating + '
'; - content += '
' + tvheadend.contentGroupLookupName(event.content_type) + '
'; - - if (event.ext_desc != null) - content += '
' + event.ext_desc + '
'; - - if (event.ext_item != null) - content += '
' + event.ext_item + '
'; - - if (event.ext_text != null) - content += '
' + event.ext_text + '
'; + if (event.episodeOnscreen) + content += '
' + event.episodeOnscreen + '
'; + if (event.summary) + content += '
' + event.summary + '
'; + if (event.description) + content += '

' + event.description + '

'; + if (event.starRating) + content += '
Star Rating: ' + event.starRating + '
'; + if (event.ageRating) + content += '
Age Rating: ' + event.ageRating + '
'; + if (event.genre) { + var genre = []; + Ext.each(event.genre, function(g) { + var g1 = tvheadend.contentGroupLookupName(g); + var g2 = tvheadend.contentGroupFullLookupName(g); + if (g1 == g2) + g1 = ''; + if (g1 || g2) + genre.push((g1 ? '[' + g1 + '] ' : '') + g2); + }); + content += '
Content Type: ' + genre.join(', ') + '
'; + } content += '
Search IMDB
'; content += ''; content += '
'; now = new Date(); - if (event.start < now && event.end > now) { + if (event.start < now && event.stop > now) { var title = event.title; - if (event.episode) - title += ' / ' + event.episode; - content += '
Play
'; } @@ -158,7 +172,7 @@ tvheadend.epgDetails = function(event) { })); buttons.push(new Ext.Button({ handler: recordSeries, - text: event.serieslink ? "Record series" : "Autorec" + text: event.serieslinkId ? "Record series" : "Autorec" })); } else { @@ -177,6 +191,7 @@ tvheadend.epgDetails = function(event) { constrainHeader: true, buttons: buttons, buttonAlign: 'center', + autoScroll: true, html: content }); win.show(); @@ -193,7 +208,7 @@ tvheadend.epgDetails = function(event) { Ext.Ajax.request({ url: url, params: { - event_id: event.id, + event_id: event.eventId, config_uuid: confcombo.getValue() }, success: function(response, options) { @@ -212,55 +227,46 @@ tvheadend.epg = function() { width: 20, dataIndex: 'actions', actions: [{ - iconIndex: 'schedstate' + iconIndex: 'dvrState' }] }); var epgStore = new Ext.ux.grid.livegrid.Store({ autoLoad: true, - url: 'epg', + url: 'api/epg/events/grid', bufferSize: 300, reader: new Ext.ux.grid.livegrid.JsonReader({ root: 'entries', totalProperty: 'totalCount', - id: 'id' - }, [{ - name: 'id' - }, { - name: 'channel' - }, { - name: 'channelid' - }, { - name: 'title' - }, { - name: 'subtitle' - }, { - name: 'episode' - }, { - name: 'description' - }, { - name: 'chicon' - }, { + id: 'eventId', + }, + [ + { name: 'eventId' }, + { name: 'channelName' }, + { name: 'channelUuid' }, + { name: 'channelNumber' }, + { name: 'channelIcon' }, + { name: 'title' }, + { name: 'subtitle' }, + { name: 'summary' }, + { name: 'description' }, + { name: 'episodeOnscreen' }, + { name: 'start', type: 'date', dateFormat: 'U' /* unix time */ - }, { - name: 'end', + }, + { + name: 'stop', type: 'date', dateFormat: 'U' /* unix time */ - }, { - name: 'duration' - }, { - name: 'starrating' - }, { - name: 'agerating' - }, { - name: 'content_type' - }, { - name: 'schedstate' - }, { - name: 'serieslink' - }]) + }, + { name: 'starRating' }, + { name: 'ageRating' }, + { name: 'genre' }, + { name: 'dvrState' }, + { name: 'serieslinkId' }, + ]), }); function setMetaAttr(meta, record) { @@ -275,14 +281,21 @@ tvheadend.epg = function() { function renderDate(value, meta, record, rowIndex, colIndex, store) { setMetaAttr(meta, record); - var dt = new Date(value); - return dt.format('D, M d, H:i'); + if (value) { + var dt = new Date(value); + return dt.format('D, M d, H:i'); + } + return ""; } function renderDuration(value, meta, record, rowIndex, colIndex, store) { setMetaAttr(meta, record); - value = Math.floor(value / 60); + value = record.data.stop - record.data.start; + if (!value || value < 0) + value = 0; + + value = Math.floor(value / 60000); if (value >= 60) { var min = value % 60; @@ -310,90 +323,131 @@ tvheadend.epg = function() { return '' + value; } - var epgCm = new Ext.grid.ColumnModel([actions, - new Ext.ux.grid.ProgressColumn({ - width: 100, - header: "Progress", - dataIndex: 'progress', - colored: false, - ceiling: 100, - tvh_renderer: function(value, meta, record, rowIndex, colIndex, store) { - var entry = record.data; - var start = entry.start; - var end = entry.end; - var duration = entry.duration; // seconds - var now = new Date(); + var epgCm = new Ext.grid.ColumnModel({ + defaultSortable: true, + columns: [ + actions, + new Ext.ux.grid.ProgressColumn({ + width: 100, + header: "Progress", + dataIndex: 'progress', + colored: false, + ceiling: 100, + tvh_renderer: function(value, meta, record, rowIndex, colIndex, store) { + var entry = record.data; + var start = entry.start; // milliseconds + var duration = entry.stop - start; // milliseconds + var now = new Date(); - // Only render a progress bar for currently running programmes - if (now >= start) - return (now - start) / 1000 / duration * 100; - else - return ""; + if (!duration || duration < 0) duration = 0; + // Only render a progress bar for currently running programmes + if (now >= start && now - start <= duration) + return (now - start) / duration * 100; + else + return ""; + } + }), + { + width: 250, + id: 'title', + header: "Title", + dataIndex: 'title', + renderer: renderText + }, + { + width: 250, + id: 'subtitle', + header: "SubTitle", + dataIndex: 'subtitle', + renderer: renderText + }, + { + width: 100, + id: 'episodeOnscreen', + header: "Episode", + dataIndex: 'episodeOnscreen', + renderer: renderText + }, + { + width: 100, + id: 'start', + header: "Start", + dataIndex: 'start', + renderer: renderDate + }, + { + width: 100, + hidden: true, + id: 'stop', + header: "End", + dataIndex: 'stop', + renderer: renderDate + }, + { + width: 100, + id: 'duration', + header: "Duration", + renderer: renderDuration + }, + { + width: 60, + id: 'channelNumber', + header: "Number", + align: 'right', + dataIndex: 'channelNumber', + renderer: renderText + }, + { + width: 250, + id: 'channelName', + header: "Channel", + dataIndex: 'channelName', + renderer: renderText + }, + { + width: 50, + id: 'starRating', + header: "Stars", + dataIndex: 'starRating', + renderer: renderInt + }, + { + width: 50, + id: 'ageRating', + header: "Age", + dataIndex: 'ageRating', + renderer: renderInt + }, { + width: 250, + id: 'genre', + header: "Content Type", + dataIndex: 'genre', + renderer: function(vals) { + var r = []; + Ext.each(vals, function(v) { + v = tvheadend.contentGroupFullLookupName(v); + if (v) + r.push(v); + }); + return r.join(','); + } } - }), { - width: 250, - id: 'title', - header: "Title", - dataIndex: 'title', - renderer: renderText - }, { - width: 250, - id: 'subtitle', - header: "SubTitle", - dataIndex: 'subtitle', - renderer: renderText - }, { - width: 100, - id: 'episode', - header: "Episode", - dataIndex: 'episode', - renderer: renderText - }, { - width: 100, - id: 'start', - header: "Start", - dataIndex: 'start', - renderer: renderDate - }, { - width: 100, - hidden: true, - id: 'end', - header: "End", - dataIndex: 'end', - renderer: renderDate - }, { - width: 100, - id: 'duration', - header: "Duration", - dataIndex: 'duration', - renderer: renderDuration - }, { - width: 250, - id: 'channel', - header: "Channel", - dataIndex: 'channel', - renderer: renderText - }, { - width: 50, - id: 'starrating', - header: "Stars", - dataIndex: 'starrating', - renderer: renderInt - }, { - width: 50, - id: 'agerating', - header: "Age", - dataIndex: 'agerating', - renderer: renderInt - }, { - width: 250, - id: 'content_type', - header: "Content Type", - dataIndex: 'content_type', - renderer: function(v) { - return tvheadend.contentGroupLookupName(v); - } - }]); + ] + }); + + var filter = new Ext.ux.grid.GridFilters({ + encode: true, + local: false, + filters: [ + { type: 'string', dataIndex: 'title' }, + { type: 'string', dataIndex: 'subtitle' }, + { type: 'string', dataIndex: 'episodeOnscreen' }, + { type: 'intsplit', dataIndex: 'channelNumber', intsplit: 1000000 }, + { type: 'string', dataIndex: 'channelName' }, + { type: 'numeric', dataIndex: 'starRating' }, + { type: 'numeric', dataIndex: 'ageRating' } + ] + }); // Title search box @@ -419,7 +473,7 @@ tvheadend.epg = function() { blur: function () { if(this.getRawValue() == "" ) { clearChannelFilter(); - epgStore.reload(); + epgView.reset(); } } } @@ -441,7 +495,7 @@ tvheadend.epg = function() { blur: function () { if(this.getRawValue() == "" ) { clearChannelTagsFilter(); - epgStore.reload(); + epgView.reset(); } } } @@ -465,7 +519,7 @@ tvheadend.epg = function() { blur: function () { if(this.getRawValue() == "" ) { clearContentGroupFilter(); - epgStore.reload(); + epgView.reset(); } } } @@ -486,7 +540,7 @@ tvheadend.epg = function() { blur: function () { if(this.getRawValue() == "" ) { clearDurationFilter(); - epgStore.reload(); + epgView.reset(); } } } @@ -508,18 +562,18 @@ tvheadend.epg = function() { }; clearChannelTagsFilter = function() { - delete epgStore.baseParams.tag; + delete epgStore.baseParams.channelTag; epgFilterChannelTags.setValue(""); }; clearContentGroupFilter = function() { - delete epgStore.baseParams.content_type; + delete epgStore.baseParams.contentType; epgFilterContentGroup.setValue(""); }; clearDurationFilter = function() { - delete epgStore.baseParams.minduration; - delete epgStore.baseParams.maxduration; + delete epgStore.baseParams.durationMin; + delete epgStore.baseParams.durationMax; epgFilterDuration.setValue(""); }; @@ -529,7 +583,9 @@ tvheadend.epg = function() { clearChannelTagsFilter(); clearDurationFilter(); clearContentGroupFilter(); - epgStore.reload(); + filter.clearFilters(); + delete epgStore.sortInfo; + epgView.reset(); }; /* @@ -541,33 +597,33 @@ tvheadend.epg = function() { clearChannelFilter(); else if (epgStore.baseParams.channel !== r.data.key) epgStore.baseParams.channel = r.data.key; - epgStore.reload(); + epgView.reset(); }); epgFilterChannelTags.on('select', function(c, r) { if (r.data.key == -1) clearChannelTagsFilter(); - else if (epgStore.baseParams.tag !== r.data.key) - epgStore.baseParams.tag = r.data.key; - epgStore.reload(); + else if (epgStore.baseParams.channelTag !== r.data.key) + epgStore.baseParams.channelTag = r.data.key; + epgView.reset(); }); epgFilterContentGroup.on('select', function(c, r) { if (r.data.key == -1) clearContentGroupFilter(); - else if (epgStore.baseParams.content_type !== r.data.key) - epgStore.baseParams.content_type = r.data.key; - epgStore.reload(); + else if (epgStore.baseParams.contentType !== r.data.key) + epgStore.baseParams.contentType = r.data.key; + epgView.reset(); }); epgFilterDuration.on('select', function(c, r) { if (r.data.identifier == -1) clearDurationFilter(); - else if (epgStore.baseParams.minduration !== r.data.minvalue) { - epgStore.baseParams.minduration = r.data.minvalue; - epgStore.baseParams.maxduration = r.data.maxvalue; + else if (epgStore.baseParams.durationMin !== r.data.minvalue) { + epgStore.baseParams.durationMin = r.data.minvalue; + epgStore.baseParams.durationMax = r.data.maxvalue; } - epgStore.reload(); + epgView.reset(); }); epgFilterTitle.on('valid', function(c) { @@ -578,7 +634,7 @@ tvheadend.epg = function() { if (epgStore.baseParams.title !== value) { epgStore.baseParams.title = value; - epgStore.reload(); + epgView.reset(); } }); @@ -586,6 +642,14 @@ tvheadend.epg = function() { nearLimit: 100, loadMask: { msg: 'Buffering. Please wait...' + }, + listeners: { + beforebuffer: { + fn: function(view, ds, index, range, total, options) { + /* filters hack */ + filter.onBeforeLoad(ds, options); + } + } } }); @@ -632,7 +696,7 @@ tvheadend.epg = function() { stateId: 'epggrid', enableDragDrop: false, cm: epgCm, - plugins: [actions], + plugins: [filter, actions], title: 'Electronic Program Guide', iconCls: 'newspaper', store: epgStore, @@ -646,7 +710,10 @@ tvheadend.epg = function() { }); panel.on('rowclick', rowclicked); - + panel.on('filterupdate', function() { + epgView.reset(); + }); + /** * Listener for DVR notifications. We want to update the EPG grid when a * recording is finished/deleted etc. so the status icon gets updated. @@ -660,7 +727,7 @@ tvheadend.epg = function() { // Always reload the store when the tab is activated panel.on('beforeshow', function() { - this.store.reload(); + epgStore.reload(); }); function rowclicked(grid, index) { @@ -676,11 +743,11 @@ tvheadend.epg = function() { : "Don't care"; var channel = epgStore.baseParams.channel ? tvheadend.channelLookupName(epgStore.baseParams.channel) : "Don't care"; - var tag = epgStore.baseParams.tag ? tvheadend.tagLookupName(epgStore.baseParams.tag) + var tag = epgStore.baseParams.channelTag ? tvheadend.channelTagLookupName(epgStore.baseParams.channelTag) : "Don't care"; - var content_type = epgStore.baseParams.content_type ? tvheadend.contentGroupLookupName(epgStore.baseParams.content_type) + var contentType = epgStore.baseParams.contentType ? tvheadend.contentGroupLookupName(epgStore.baseParams.contentType) : "Don't care"; - var duration = epgStore.baseParams.minduration ? tvheadend.durationLookupRange(epgStore.baseParams.minduration) + var duration = epgStore.baseParams.durationMin ? tvheadend.durationLookupRange(epgStore.baseParams.durationMin) : "Don't care"; Ext.MessageBox.confirm('Auto Recorder', 'This will create an automatic rule that ' @@ -689,7 +756,7 @@ tvheadend.epg = function() { + '
Title:
' + title + '
' + '
Channel:
' + channel + '
' + '
Tag:
' + tag + '
' - + '
Genre:
' + content_type + '
' + + '
Genre:
' + contentType + '
' + '
Duration:
' + duration + '
' + '

' + 'Currently this will match (and record) ' + epgStore.getTotalCount() + ' events. ' + 'Are you sure?', @@ -709,9 +776,9 @@ tvheadend.epg = function() { if (params.title) conf.title = params.title; if (params.channel) conf.channel = params.channel; if (params.tag) conf.tag = params.tag; - if (params.content_type) conf.content_type = params.content_type; - if (params.minduration) conf.minduration = params.minduration; - if (params.maxduration) conf.maxduration = params.maxduration; + if (params.contentType) conf.content_type = params.contentType; + if (params.durationMin) conf.minduration = params.durationMin; + if (params.durationMax) conf.maxduration = params.durationMax; Ext.Ajax.request({ url: 'api/dvr/autorec/create', params: { conf: Ext.encode(conf) }