epg: query API rewrite

This commit is contained in:
Jaroslav Kysela 2014-09-25 22:19:07 +02:00
parent ba136c5392
commit 0cb841a427
10 changed files with 1143 additions and 592 deletions

View file

@ -25,7 +25,7 @@
#include "redblack.h"
#include "access.h"
#define TVH_API_VERSION 14
#define TVH_API_VERSION 15
/*
* Command hook

View file

@ -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 },
};

View file

@ -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;

View file

@ -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;

436
src/epg.c
View file

@ -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

View file

@ -19,6 +19,7 @@
#ifndef EPG_H
#define EPG_H
#include <regex.h>
#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

View file

@ -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;
}

View file

@ -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

View file

@ -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, "<html>");
@ -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, "<b>No matching entries found</b>");
} 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, "<hr>");
epg_query_free(&eqr);
epg_query_free(&eq);
}

View file

@ -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 += '<img class="x-epg-chicon" src="' + event.chicon + '">';
if (event.channelIcon != null && event.channelIcon.length > 0)
content += '<img class="x-epg-chicon" src="' + event.channelIcon + '">';
content += '<div class="x-epg-title">' + event.title;
if (event.subtitle)
content += "&nbsp;:&nbsp;" + event.subtitle;
content += '</div>';
content += '<div class="x-epg-desc">' + event.episode + '</div>';
content += '<div class="x-epg-desc">' + event.description + '</div>';
content += '<div class="x-epg-meta">' + event.starrating + '</div>';
content += '<div class="x-epg-meta">' + event.agerating + '</div>';
content += '<div class="x-epg-meta">' + tvheadend.contentGroupLookupName(event.content_type) + '</div>';
if (event.ext_desc != null)
content += '<div class="x-epg-meta">' + event.ext_desc + '</div>';
if (event.ext_item != null)
content += '<div class="x-epg-meta">' + event.ext_item + '</div>';
if (event.ext_text != null)
content += '<div class="x-epg-meta">' + event.ext_text + '</div>';
if (event.episodeOnscreen)
content += '<div class="x-epg-desc">' + event.episodeOnscreen + '</div>';
if (event.summary)
content += '<div class="x-epg-desc"><b>' + event.summary + '</b></div>';
if (event.description)
content += '<div class="x-epg-desc"><p>' + event.description + '</p></div>';
if (event.starRating)
content += '<div class="x-epg-meta">Star Rating: ' + event.starRating + '</div>';
if (event.ageRating)
content += '<div class="x-epg-meta">Age Rating: ' + event.ageRating + '</div>';
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 += '<div class="x-epg-meta">Content Type: ' + genre.join(', ') + '</div>';
}
content += '<div class="x-epg-meta"><a target="_blank" href="http://akas.imdb.com/find?q=' + event.title + '">Search IMDB</a></div>';
content += '<div id="related"></div>';
content += '<div id="altbcast"></div>';
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 += '<div class="x-epg-meta"><a href="play/stream/channelid/' + event.channelid +
if (event.episodeOnscreen)
title += ' / ' + event.episodeOnscreen;
content += '<div class="x-epg-meta"><a href="play/stream/channel/' + event.channelUuid +
'?title=' + encodeURIComponent(title) + '">Play</a></div>';
}
@ -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() {
: "<i>Don't care</i>";
var channel = epgStore.baseParams.channel ? tvheadend.channelLookupName(epgStore.baseParams.channel)
: "<i>Don't care</i>";
var tag = epgStore.baseParams.tag ? tvheadend.tagLookupName(epgStore.baseParams.tag)
var tag = epgStore.baseParams.channelTag ? tvheadend.channelTagLookupName(epgStore.baseParams.channelTag)
: "<i>Don't care</i>";
var content_type = epgStore.baseParams.content_type ? tvheadend.contentGroupLookupName(epgStore.baseParams.content_type)
var contentType = epgStore.baseParams.contentType ? tvheadend.contentGroupLookupName(epgStore.baseParams.contentType)
: "<i>Don't care</i>";
var duration = epgStore.baseParams.minduration ? tvheadend.durationLookupRange(epgStore.baseParams.minduration)
var duration = epgStore.baseParams.durationMin ? tvheadend.durationLookupRange(epgStore.baseParams.durationMin)
: "<i>Don't care</i>";
Ext.MessageBox.confirm('Auto Recorder', 'This will create an automatic rule that '
@ -689,7 +756,7 @@ tvheadend.epg = function() {
+ '<div class="x-smallhdr">Title:</div>' + title + '<br>'
+ '<div class="x-smallhdr">Channel:</div>' + channel + '<br>'
+ '<div class="x-smallhdr">Tag:</div>' + tag + '<br>'
+ '<div class="x-smallhdr">Genre:</div>' + content_type + '<br>'
+ '<div class="x-smallhdr">Genre:</div>' + contentType + '<br>'
+ '<div class="x-smallhdr">Duration:</div>' + duration + '<br>'
+ '<br><br>' + '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) }