From e544180acf9fdf1f54ffb6a6ccfd4e40a9614fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Tue, 26 Aug 2008 14:15:33 +0000 Subject: [PATCH] Add channel configuration and setup. htsclient.c is now no longer supported. --- Makefile | 2 +- autorec.c | 4 +- avgen.c | 2 +- channels.c | 216 +++++-------- channels.h | 10 +- dvb/dvb_transport.c | 24 +- epg.c | 5 +- epg_xmltv.c | 6 +- ffmuxer.c | 1 - file_input.c | 2 +- htsclient.c | 575 --------------------------------- htsclient.h | 26 -- htsp.c | 13 +- htsp_muxer.c | 8 +- iptv_output.c | 2 +- main.c | 2 - psi.c | 2 +- pvr.c | 3 +- rpc.c | 6 +- rtsp.c | 2 +- serviceprobe.c | 2 +- transports.c | 2 +- tvhead.h | 17 +- webui/extjs.c | 221 ++++++++++++- webui/static/app/chconf.js | 361 +++++++++++++++++++++ webui/static/app/dvb.js | 1 + webui/static/app/extensions.js | 132 ++++++++ webui/static/app/tvheadend.js | 3 +- 28 files changed, 850 insertions(+), 800 deletions(-) delete mode 100644 htsclient.c delete mode 100644 htsclient.h create mode 100644 webui/static/app/chconf.js diff --git a/Makefile b/Makefile index ac713add..f3699845 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ SRCS += iptv_input.c iptv_output.c SRCS += avgen.c file_input.c -SRCS += htsclient.c rtsp.c rtp.c +SRCS += rtsp.c rtp.c SRCS += v4l.c diff --git a/autorec.c b/autorec.c index b8a8fbbc..43a8bbf7 100644 --- a/autorec.c +++ b/autorec.c @@ -99,7 +99,7 @@ autorec_check_new_ar(autorec_t *ar) event_t *e; channel_t *ch; - RB_FOREACH(ch, &channel_tree, ch_global_link) { + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { e = ch->ch_epg_cur_event; if(e == NULL) continue; @@ -306,7 +306,7 @@ autorec_load(void) contentgroup ? epg_content_group_find_by_name(contentgroup) : NULL, - channel ? channel_find(channel, 1) : NULL, + channel ? channel_find_by_name(channel, 1) : NULL, id, creator); if(id > ar_id_ceil) diff --git a/avgen.c b/avgen.c index b0020ed8..edc0bd74 100644 --- a/avgen.c +++ b/avgen.c @@ -98,7 +98,7 @@ avgen_init(void) if(avcodec_find_encoder(CODEC_ID_MP2) == NULL) return; - ch = channel_find("Test PAL", 1); + ch = channel_find_by_name("Test PAL", 1); t = calloc(1, sizeof(th_transport_t)); diff --git a/channels.c b/channels.c index 9670435e..4823ce62 100644 --- a/channels.c +++ b/channels.c @@ -42,7 +42,8 @@ #include "pvr.h" #include "autorec.h" -struct channel_tree channel_tree; +struct channel_tree channel_name_tree; +static struct channel_tree channel_identifier_tree; static int dictcmp(const char *a, const char *b) @@ -83,6 +84,16 @@ channelcmp(const channel_t *a, const channel_t *b) } +/** + * + */ +static int +chidcmp(const channel_t *a, const channel_t *b) +{ + return a->ch_id - b->ch_id; +} + + /** * */ @@ -115,38 +126,70 @@ channel_set_name(channel_t *ch, const char *name) free((void *)n2); - x = RB_INSERT_SORTED(&channel_tree, ch, ch_global_link, channelcmp); + x = RB_INSERT_SORTED(&channel_name_tree, ch, ch_name_link, channelcmp); assert(x == NULL); } + + +/** + * + */ +static channel_t * +channel_create(const char *name) +{ + channel_t *ch, *x; + int id; + + ch = RB_LAST(&channel_identifier_tree); + if(ch == NULL) { + id = 1; + } else { + id = ch->ch_id + 1; + } + + ch = calloc(1, sizeof(channel_t)); + TAILQ_INIT(&ch->ch_epg_events); + + channel_set_name(ch, name); + + ch->ch_id = id; + x = RB_INSERT_SORTED(&channel_identifier_tree, ch, + ch_identifier_link, chidcmp); + assert(x == NULL); + return ch; +} + /** * */ channel_t * -channel_find(const char *name, int create) +channel_find_by_name(const char *name, int create) { - channel_t *ch, skel; - - skel.ch_name = name; - - if((ch = RB_FIND(&channel_tree, &skel, ch_global_link, channelcmp)) != NULL) + channel_t skel, *ch; + skel.ch_name = (char *)name; + ch = RB_FIND(&channel_name_tree, &skel, ch_name_link, channelcmp); + if(ch != NULL || create == 0) return ch; + return channel_create(name); +} - if(create == 0) - return NULL; - ch = calloc(1, sizeof(channel_t)); - ch->ch_index = channel_tree.entries; - - TAILQ_INIT(&ch->ch_epg_events); - - channel_set_name(ch, name); - - ch->ch_tag = tag_get(); +/** + * + */ +channel_t * +channel_find_by_identifier(int id) +{ + channel_t skel, *ch; + skel.ch_id = id; + ch = RB_FIND(&channel_identifier_tree, &skel, ch_identifier_link, chidcmp); return ch; } + + static struct strtab commercial_detect_tab[] = { { "none", COMMERCIAL_DETECT_NONE }, { "ttp192", COMMERCIAL_DETECT_TTP192 }, @@ -159,123 +202,11 @@ static struct strtab commercial_detect_tab[] = { void channels_load(void) { -#if 0 - struct config_head cl; - config_entry_t *ce; - char buf[PATH_MAX]; - DIR *dir; - struct dirent *d; - const char *name, *grp, *x; - channel_t *ch; - int v; - TAILQ_INIT(&all_channel_groups); - TAILQ_INIT(&cl); - - snprintf(buf, sizeof(buf), "%s/channel-group-settings.cfg", settings_dir); - config_read_file0(buf, &cl); - - TAILQ_FOREACH(ce, &cl, ce_link) { - if(ce->ce_type != CFG_SUB || strcasecmp("channel-group", ce->ce_key)) - continue; - - if((name = config_get_str_sub(&ce->ce_sub, "name", NULL)) == NULL) - continue; - - channel_group_find(name, 1); - } - config_free0(&cl); - - tcg = channel_group_find("-disabled-", 1); - tcg->tcg_cant_delete_me = 1; - tcg->tcg_hidden = 1; - - defgroup = channel_group_find("Uncategorized", 1); - defgroup->tcg_cant_delete_me = 1; - - snprintf(buf, sizeof(buf), "%s/channels", settings_dir); - - if((dir = opendir(buf)) == NULL) - return; - - while((d = readdir(dir)) != NULL) { - - if(d->d_name[0] == '.') - continue; - - snprintf(buf, sizeof(buf), "%s/channels/%s", settings_dir, d->d_name); - TAILQ_INIT(&cl); - config_read_file0(buf, &cl); - - name = config_get_str_sub(&cl, "name", NULL); - grp = config_get_str_sub(&cl, "channel-group", NULL); - if(name != NULL && grp != NULL) { - tcg = channel_group_find(grp, 1); - ch = channel_find(name, 1, tcg); - - x = config_get_str_sub(&cl, "commercial-detect", NULL); - if(x != NULL) { - v = str2val(x, commercial_detect_tab); - if(v > 1) - ch->ch_commercial_detection = v; - } - - if((x = config_get_str_sub(&cl, "icon", NULL)) != NULL) - ch->ch_icon = strdup(x); - } - config_free0(&cl); - } - - closedir(dir); - - - /* Static services */ - - TAILQ_FOREACH(ce, &config_list, ce_link) { - if(ce->ce_type == CFG_SUB && !strcasecmp("service", ce->ce_key)) { - service_load(&ce->ce_sub); - } - } -#endif } -/** - * The index stuff should go away - */ -channel_t * -channel_by_index(uint32_t index) -{ - channel_t *ch; - - RB_FOREACH(ch, &channel_tree, ch_global_link) - if(ch->ch_index == index) - return ch; - - return NULL; -} - - - -/** - * - */ -channel_t * -channel_by_tag(uint32_t tag) -{ - channel_t *ch; - - RB_FOREACH(ch, &channel_tree, ch_global_link) - if(ch->ch_tag == tag) - return ch; - - return NULL; -} - - - - /** * Write out a config file for a channel */ @@ -283,7 +214,9 @@ static void channel_save(channel_t *ch) { htsmsg_t *m = htsmsg_create(); - htsmsg_add_str(m, "icon", ch->ch_icon); + if(ch->ch_icon != NULL) + htsmsg_add_str(m, "icon", ch->ch_icon); + htsmsg_add_str(m, "commercial_detect", val2str(ch->ch_commercial_detection, commercial_detect_tab) ?: "?"); @@ -299,12 +232,15 @@ channel_rename(channel_t *ch, const char *newname) { th_transport_t *t; - if(channel_find(newname, 0)) + if(channel_find_by_name(newname, 0)) return -1; + tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" renamed to \"%s\"", + ch->ch_name, newname); + hts_settings_remove("channels/%s", ch->ch_name); - RB_REMOVE(&channel_tree, ch, ch_global_link); + RB_REMOVE(&channel_name_tree, ch, ch_name_link); channel_set_name(ch, newname); LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) { @@ -326,6 +262,9 @@ channel_delete(channel_t *ch) th_transport_t *t; th_subscription_t *s; + tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" deleted", + ch->ch_name); + pvr_destroy_by_channel(ch); while((t = LIST_FIRST(&ch->ch_transports)) != NULL) { @@ -344,11 +283,13 @@ channel_delete(channel_t *ch) hts_settings_remove("channels/%s", ch->ch_name); - free((void *)ch->ch_name); - free((void *)ch->ch_sname); + RB_REMOVE(&channel_name_tree, ch, ch_name_link); + RB_REMOVE(&channel_identifier_tree, ch, ch_identifier_link); + + free(ch->ch_name); + free(ch->ch_sname); free(ch->ch_icon); - RB_REMOVE(&channel_tree, ch, ch_global_link); free(ch); } @@ -363,6 +304,9 @@ void channel_merge(channel_t *dst, channel_t *src) { th_transport_t *t; + + tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" merged into \"%s\"", + src->ch_name, dst->ch_name); while((t = LIST_FIRST(&src->ch_transports)) != NULL) { transport_unmap_channel(t); diff --git a/channels.h b/channels.h index f2106c83..08d514af 100644 --- a/channels.h +++ b/channels.h @@ -21,18 +21,12 @@ void channels_load(void); -channel_t *channel_by_index(uint32_t id); +channel_t *channel_find_by_name(const char *name, int create); -channel_t *channel_by_tag(uint32_t tag); - -int id_by_channel(channel_t *ch); - -int channel_get_channels(void); +channel_t *channel_find_by_identifier(int id); void channel_unsubscribe(th_subscription_t *s); -channel_t *channel_find(const char *name, int create); - void channel_set_teletext_rundown(channel_t *ch, int v); void channel_settings_write(channel_t *ch); diff --git a/dvb/dvb_transport.c b/dvb/dvb_transport.c index d9da18a0..04741fdf 100644 --- a/dvb/dvb_transport.c +++ b/dvb/dvb_transport.c @@ -156,18 +156,23 @@ dvb_transport_quality(th_transport_t *t) * Generate a descriptive name for the source */ static const char * -dvb_transport_name(th_transport_t *t) +dvb_transport_sourcename(th_transport_t *t) { - th_dvb_mux_instance_t *tdmi; - static char buf[200]; + th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance; - tdmi = t->tht_dvb_mux_instance; + return tdmi->tdmi_adapter->tda_displayname; +} - snprintf(buf, sizeof(buf), "\"%s\" on \"%s\"", - tdmi->tdmi_network ?: "Unknown network", - tdmi->tdmi_adapter->tda_rootpath); - return buf; +/** + * Generate a descriptive name for the source + */ +static const char * +dvb_transport_networkname(th_transport_t *t) +{ + th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance; + + return tdmi->tdmi_network; } @@ -209,7 +214,8 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid, t->tht_start_feed = dvb_start_feed; t->tht_stop_feed = dvb_stop_feed; t->tht_config_change = dvb_transport_save; - t->tht_sourcename = dvb_transport_name; + t->tht_sourcename = dvb_transport_sourcename; + t->tht_networkname = dvb_transport_networkname; t->tht_dvb_mux_instance = tdmi; t->tht_quality_index = dvb_transport_quality; diff --git a/epg.c b/epg.c index 9f2f8494..139a2836 100644 --- a/epg.c +++ b/epg.c @@ -26,7 +26,6 @@ #include "channels.h" #include "epg.h" #include "dispatch.h" -#include "htsclient.h" #include "htsp.h" #include "autorec.h" @@ -413,7 +412,7 @@ epg_channel_maintain(void *aux, int64_t clk) now = dispatch_clock; - RB_FOREACH(ch, &channel_tree, ch_global_link) { + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { /* Age out any old events */ @@ -559,7 +558,7 @@ epg_search(struct event_list *h, const char *title, regcomp(&preg, title, REG_ICASE | REG_EXTENDED | REG_NOSUB)) return -1; - RB_FOREACH(ch, &channel_tree, ch_global_link) { + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { if(LIST_FIRST(&ch->ch_transports) == NULL) continue; diff --git a/epg_xmltv.c b/epg_xmltv.c index 2dcb07b4..27aef910 100644 --- a/epg_xmltv.c +++ b/epg_xmltv.c @@ -250,7 +250,7 @@ xmltv_resolve_by_events(xmltv_channel_t *xc) if(ex == NULL) break; - RB_FOREACH(ch, &channel_tree, ch_global_link) { + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { ec = epg_event_find_by_time0(&ch->ch_epg_events, now); cnt = 0; @@ -289,14 +289,14 @@ xmltv_transfer_events(xmltv_grabber_t *xg) continue; if(xc->xc_channel != NULL) { - ch = channel_find(xc->xc_channel, 0); + ch = channel_find_by_name(xc->xc_channel, 0); if(ch == NULL) continue; } else { how = 0; - ch = channel_find(xc->xc_displayname, 0); + ch = channel_find_by_name(xc->xc_displayname, 0); if(ch == NULL) { ch = xmltv_resolve_by_events(xc); diff --git a/ffmuxer.c b/ffmuxer.c index ab65ec5c..1bc5a855 100644 --- a/ffmuxer.c +++ b/ffmuxer.c @@ -39,7 +39,6 @@ #include "tvhead.h" #include "channels.h" #include "subscriptions.h" -#include "htsclient.h" #include "ffmuxer.h" #include "buffer.h" diff --git a/file_input.c b/file_input.c index 39f1121f..83a7a67e 100644 --- a/file_input.c +++ b/file_input.c @@ -159,7 +159,7 @@ file_input_init(void) if((s = config_get_str_sub(&ce->ce_sub, "channel", NULL)) == NULL) continue; - ch = channel_find(s, 1); + ch = channel_find_by_name(s, 1); t->tht_name = strdup(ch->ch_name); diff --git a/htsclient.c b/htsclient.c deleted file mode 100644 index 3f0ebd9a..00000000 --- a/htsclient.c +++ /dev/null @@ -1,575 +0,0 @@ -/* - * tvheadend, (simple) client interface - * Copyright (C) 2007 Andreas Öman - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "tvhead.h" -#include "channels.h" -#include "subscriptions.h" -#include "pvr.h" -#include "epg.h" -#include "teletext.h" -#include "dispatch.h" -#include "buffer.h" -#include "tsmux.h" -#include "tcp.h" -#include "htsclient.h" - -LIST_HEAD(client_list, client); - -struct client_list all_clients; - - -/* - * Client - */ - -typedef struct client { - tcp_session_t c_tcp_session; - - LIST_ENTRY(client) c_global_link; - int c_streamfd; - pthread_t c_ptid; - - LIST_HEAD(, th_subscription) c_subscriptions; - - struct in_addr c_ipaddr; - int c_port; - - struct ref_update_queue c_refq; - - dtimer_t c_status_timer; - - void *c_muxer; - -} client_t; - - - -void client_status_update(void *aux, int64_t now); - -#define cprintf(c, fmt...) tcp_printf(&(c)->c_tcp_session, fmt) - - -static void -client_output_ts(void *opaque, th_subscription_t *s, - uint8_t *pkt, int blocks, int64_t pcr) -{ - struct msghdr msg; - struct iovec vec[2]; - int r; - client_t *c = opaque; - struct sockaddr_in sin; - char hdr[2]; - - - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = htons(c->c_port); - sin.sin_addr = c->c_ipaddr; - - hdr[0] = HTSTV_TRANSPORT_STREAM; - hdr[1] = s->ths_u32; - - vec[0].iov_base = hdr; - vec[0].iov_len = 2; - vec[1].iov_base = pkt; - vec[1].iov_len = blocks * 188; - - memset(&msg, 0, sizeof(msg)); - msg.msg_name = &sin; - msg.msg_namelen = sizeof(struct sockaddr_in); - msg.msg_iov = vec; - msg.msg_iovlen = 2; - - r = sendmsg(c->c_streamfd, &msg, 0); - if(r < 0) - perror("sendmsg"); -} - - -/* - * - */ - -static int -cr_channel_info(client_t *c, char **argv, int argc) -{ - channel_t *ch; - - if(argc < 1) - return 1; - - if((ch = channel_by_index(atoi(argv[0]))) == NULL) - return 1; - - cprintf(c, - "displayname = %s\n" - "icon = %s\n" - "tag = %d\n", - ch->ch_name, - ch->ch_icon ? ch->ch_icon : "", - ch->ch_tag); - - return 0; -} - - -/* - * - */ - -static int -cr_channel_unsubscribe(client_t *c, char **argv, int argc) -{ - th_subscription_t *s; - int chindex; - - if(argc < 1) - return 1; - - chindex = atoi(argv[0]); - - LIST_FOREACH(s, &c->c_subscriptions, ths_subscriber_link) { - if(s->ths_u32 == chindex) - break; - } - - if(s == NULL) - return 1; - - LIST_REMOVE(s, ths_subscriber_link); - subscription_unsubscribe(s); - return 0; -} - - -/* - * Called when a subscription gets/loses access to a transport - */ -static void -client_subscription_callback(struct th_subscription *s, - subscription_event_t event, void *opaque) -{ - client_t *c = opaque; - - switch(event) { - case TRANSPORT_AVAILABLE: - assert(c->c_muxer == NULL); - c->c_muxer = ts_muxer_init(s, client_output_ts, c, - TS_HTSCLIENT | TS_SEEK, 0); - ts_muxer_play(c->c_muxer, 0); - break; - - case TRANSPORT_UNAVAILABLE: - assert(c->c_muxer != NULL); - ts_muxer_deinit(c->c_muxer, s); - c->c_muxer = NULL; - break; - } -} - -/* - * - */ -static int -cr_channel_subscribe(client_t *c, char **argv, int argc) -{ - channel_t *ch; - th_subscription_t *s; - unsigned int chindex, weight; - char tmp[100]; - struct sockaddr_in *si; - - if(argc < 1) - return 1; - - chindex = atoi(argv[0]); - - weight = argc > 1 ? atoi(argv[1]) : 100; - - LIST_FOREACH(s, &c->c_subscriptions, ths_subscriber_link) { - if(s->ths_u32 == chindex) { - subscription_set_weight(s, weight); - return 0; - } - } - - if((ch = channel_by_index(chindex)) == NULL) - return 1; - - si = (struct sockaddr_in *)&c->c_tcp_session.tcp_peer_addr; - - snprintf(tmp, sizeof(tmp), "HTS Client @ %s", - inet_ntoa(si->sin_addr)); - - s = subscription_create(ch, weight, tmp, client_subscription_callback, c, 0); - if(s == NULL) - return 1; - - s->ths_u32 = ch->ch_index; - LIST_INSERT_HEAD(&c->c_subscriptions, s, ths_subscriber_link); - return 0; -} - - -/* - * - */ - -static int -cr_channels_list(client_t *c, char **argv, int argc) -{ - channel_t *ch; - - RB_FOREACH(ch, &channel_tree, ch_global_link) { - cprintf(c, "channel = %d\n", ch->ch_index); - } - return 0; -} - - -/* - * - */ - -static int -cr_streamport(client_t *c, char **argv, int argc) -{ - if(argc < 2) - return 1; - - if(c->c_streamfd == -1) - c->c_streamfd = socket(AF_INET, SOCK_DGRAM, 0); - - c->c_ipaddr.s_addr = inet_addr(argv[0]); - c->c_port = atoi(argv[1]); - - tvhlog(LOG_INFO, "htsclient", - "%s registers UDP stream target %s:%d", - tcp_logname(&c->c_tcp_session), inet_ntoa(c->c_ipaddr), c->c_port); - - return 0; -} - -/* - * - */ - -static int -cr_event_info(client_t *c, char **argv, int argc) -{ - event_t *e = NULL, *x; - uint32_t tag, prev, next; - channel_t *ch; - pvr_rec_t *pvrr; - - if(argc < 2) - return 1; - - if(!strcasecmp(argv[0], "tag")) - e = epg_event_find_by_tag(atoi(argv[1])); - if(!strcasecmp(argv[0], "now")) - if((ch = channel_by_index(atoi(argv[1]))) != NULL) - e = epg_event_get_current(ch); - if(!strcasecmp(argv[0], "at") && argc == 3) - if((ch = channel_by_index(atoi(argv[1]))) != NULL) - e = epg_event_find_by_time(ch, atoi(argv[2])); - - if(e == NULL) { - return 1; - } - - tag = e->e_tag; - x = TAILQ_PREV(e, event_queue, e_channel_link); - prev = x != NULL ? x->e_tag : 0; - - x = TAILQ_NEXT(e, e_channel_link); - next = x != NULL ? x->e_tag : 0; - - pvrr = pvr_get_by_entry(e); - - cprintf(c, - "start = %ld\n" - "stop = %ld\n" - "title = %s\n" - "desc = %s\n" - "tag = %u\n" - "prev = %u\n" - "next = %u\n" - "pvrstatus = %d\n", - - e->e_start, - e->e_start + e->e_duration, - e->e_title ?: "", - e->e_desc ?: "", - tag, - prev, - next, - pvrr != NULL ? pvrr->pvrr_status : HTSTV_PVR_STATUS_NONE); - - return 0; -} - -/* - * - */ - -static int -cr_event_record(client_t *c, char **argv, int argc) -{ - event_t *e; - - if(argc < 1) - return 1; - - e = epg_event_find_by_tag(atoi(argv[0])); - if(e == NULL) { - return 1; - } - - pvr_schedule_by_event(e, "htsclient"); - - return 0; -} - - - - -/* - * - */ -static int -cr_channel_record(client_t *c, char **argv, int argc) -{ - channel_t *ch; - int duration; - - if(argc < 2) - return 1; - - if((ch = channel_by_index(atoi(argv[0]))) == NULL) - return 1; - - duration = atoi(argv[1]); - - pvr_schedule_by_channel_and_time(ch, duration, "htsclient"); - return 0; -} - -/* - * - */ -static int -cr_pvr_entry(client_t *c, pvr_rec_t *pvrr) -{ - event_t *e; - - if(pvrr == NULL) - return 1; - - cprintf(c, - "title = %s\n" - "start = %ld\n" - "stop = %ld\n" - "desc = %s\n" - "pvr_tag = %d\n" - "pvrstatus = %d\n" - "filename = %s\n" - "channel = %d\n", - pvrr->pvrr_title ?: "", - pvrr->pvrr_start, - pvrr->pvrr_stop, - pvrr->pvrr_desc ?: "", - pvrr->pvrr_ref, - pvrr->pvrr_status, - pvrr->pvrr_filename, - pvrr->pvrr_channel->ch_index); - - - e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start); - if(e != NULL) - cprintf(c, "event_tag = %d\n", e->e_tag); - - return 0; -} - -/* - * - */ - -static int -cr_pvr_getlog(client_t *c, char **argv, int argc) -{ - pvr_rec_t *pvrr; - - if(argc < 1) - return 1; - - pvrr = pvr_get_log_entry(atoi(argv[0])); - return cr_pvr_entry(c, pvrr); -} - - -/* - * - */ - -static int -cr_pvr_gettag(client_t *c, char **argv, int argc) -{ - pvr_rec_t *pvrr; - - if(argc < 1) - return 1; - - pvrr = pvr_get_tag_entry(atoi(argv[0])); - return cr_pvr_entry(c, pvrr); -} - - -/* - * - */ - -const struct { - const char *name; - int (*func)(client_t *c, char *argv[], int argc); -} cr_cmds[] = { - { "streamport", cr_streamport }, - { "channels.list", cr_channels_list }, - { "channel.info", cr_channel_info }, - { "channel.subscribe", cr_channel_subscribe }, - { "channel.unsubscribe", cr_channel_unsubscribe }, - { "channel.record", cr_channel_record }, - { "event.info", cr_event_info }, - { "event.record", cr_event_record }, - { "pvr.getlog", cr_pvr_getlog }, - { "pvr.gettag", cr_pvr_gettag }, -}; - - -static int -client_req(void *aux, char *buf) -{ - client_t *c = aux; - int i, l, x; - const char *n; - char *argv[40]; - int argc = 0; - - for(i = 0; i < sizeof(cr_cmds) / sizeof(cr_cmds[0]); i++) { - n = cr_cmds[i].name; - l = strlen(n); - if(!strncasecmp(buf, n, l) && (buf[l] == ' ' || buf[l] == 0)) { - buf += l; - - while(*buf) { - if(*buf < 33) { - buf++; - continue; - } - argv[argc++] = buf; - while(*buf > 32) - buf++; - if(*buf == 0) - break; - *buf++ = 0; - } - x = cr_cmds[i].func(c, argv, argc); - - if(x >= 0) - cprintf(c, "eom %s\n", x ? "error" : "ok"); - - return 0; - } - } - cprintf(c, "eom nocommand\n"); - return 0; -} - -/* - * client disconnect - */ -static void -client_disconnect(client_t *c) -{ - th_subscription_t *s; - - dtimer_disarm(&c->c_status_timer); - - if(c->c_streamfd != -1) - close(c->c_streamfd); - - LIST_REMOVE(c, c_global_link); - - while((s = LIST_FIRST(&c->c_subscriptions)) != NULL) { - LIST_REMOVE(s, ths_subscriber_link); - subscription_unsubscribe(s); - } -} - - -/* - * - */ -static void -htsclient_tcp_callback(tcpevent_t event, void *tcpsession) -{ - client_t *c = tcpsession; - - switch(event) { - case TCP_CONNECT: - TAILQ_INIT(&c->c_refq); - LIST_INSERT_HEAD(&all_clients, c, c_global_link); - c->c_streamfd = -1; - break; - - case TCP_DISCONNECT: - client_disconnect(c); - break; - - case TCP_INPUT: - tcp_line_read(&c->c_tcp_session, client_req); - break; - } -} - - -/* - * Fire up client handling - */ - -void -client_start(void) -{ - tcp_create_server(9909, sizeof(client_t), "htsclient", - htsclient_tcp_callback); -} - diff --git a/htsclient.h b/htsclient.h deleted file mode 100644 index 6d5ed224..00000000 --- a/htsclient.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * tvheadend, (simple) client interface - * Copyright (C) 2007 Andreas Öman - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef HTSCLIENT_H_ -#define HTSCLIENT_H_ - -void client_start(void); - -void clients_send_ref(int ref); - -#endif /* HTSCLIENT_H_ */ diff --git a/htsp.c b/htsp.c index 76c3a2e2..495cd00f 100644 --- a/htsp.c +++ b/htsp.c @@ -80,7 +80,7 @@ htsp_build_channel_msg(channel_t *ch, const char *method) htsmsg_add_str(msg, "method", method); htsmsg_add_str(msg, "channelName", ch->ch_name); - htsmsg_add_u32(msg, "channelTag", ch->ch_tag); + htsmsg_add_u32(msg, "channelId", ch->ch_id); if(ch->ch_icon != NULL) htsmsg_add_str(msg, "channelIcon", ch->ch_icon); @@ -102,7 +102,7 @@ htsp_send_all_channels(htsp_t *htsp) htsmsg_t *msg; channel_t *ch; - RB_FOREACH(ch, &channel_tree, ch_global_link) { + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { if(LIST_FIRST(&ch->ch_transports) == NULL) continue; @@ -140,13 +140,12 @@ htsp_subscribe(rpc_session_t *ses, htsmsg_t *in, void *opaque) channel_t *ch; th_subscription_t *s; htsmsg_t *r; + uint32_t u32; - uint32_t tag; + if(htsmsg_get_u32(in, "channelId", &u32)) + return rpc_error(ses, "missing argument: channelId"); - if(htsmsg_get_u32(in, "channelTag", &tag)) - return rpc_error(ses, "missing argument: channelTag"); - - if((ch = channel_by_tag(tag)) == NULL) + if((ch = channel_find_by_identifier(u32)) == NULL) return rpc_error(ses, "Channel not found"); LIST_FOREACH(s, &htsp->htsp_subscriptions, ths_subscriber_link) { diff --git a/htsp_muxer.c b/htsp_muxer.c index 8a2c801e..a8de6371 100644 --- a/htsp_muxer.c +++ b/htsp_muxer.c @@ -51,7 +51,7 @@ htsp_packet_input(void *opaque, th_muxstream_t *tms, th_pkt_t *pkt) */ htsmsg_add_str(m, "method", "muxpkt"); - htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag); + htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id); htsmsg_add_u64(m, "stream", tms->tms_index); htsmsg_add_u64(m, "dts", pkt->pkt_dts); @@ -89,7 +89,7 @@ htsp_subscription_callback(struct th_subscription *s, m = htsmsg_create(); htsmsg_add_str(m, "method", "subscription_start"); - htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag); + htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id); LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0) { tms->tms_index = index++; @@ -113,7 +113,7 @@ htsp_subscription_callback(struct th_subscription *s, if(htsp->htsp_zombie == 0) { m = htsmsg_create(); htsmsg_add_str(m, "method", "subscription_stop"); - htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag); + htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id); htsmsg_add_str(m, "reason", "unknown"); htsp_send_msg(htsp, m, 0); @@ -159,7 +159,7 @@ htsp_muxer_unsubscribe(htsp_t *htsp, uint32_t id) th_subscription_t *s; LIST_FOREACH(s, &htsp->htsp_subscriptions, ths_subscriber_link) { - if(s->ths_channel->ch_tag == id) + if(s->ths_channel->ch_id == id) break; } diff --git a/iptv_output.c b/iptv_output.c index 39b8d80e..c55c662c 100644 --- a/iptv_output.c +++ b/iptv_output.c @@ -133,7 +133,7 @@ output_multicast_load(struct config_head *head) if((name = config_get_str_sub(head, "channel", NULL)) == NULL) return; - ch = channel_find(name, 1); + ch = channel_find_by_name(name, 1); om = calloc(1, sizeof(output_multicast_t)); diff --git a/main.c b/main.c index 3cf2433b..2378b150 100644 --- a/main.c +++ b/main.c @@ -40,7 +40,6 @@ #include "dvb/dvb.h" #include "v4l.h" #include "channels.h" -#include "htsclient.h" #include "epg.h" #include "epg_xmltv.h" #include "pvr.h" @@ -309,7 +308,6 @@ main(int argc, char **argv) pvr_init(); output_multicast_setup(); - client_start(); p = atoi(config_get_str("http-server-port", "9981")); if(p) diff --git a/psi.c b/psi.c index 790ae90c..c991428a 100644 --- a/psi.c +++ b/psi.c @@ -618,7 +618,7 @@ psi_load_transport_settings(htsmsg_t *m, th_transport_t *t) st = transport_add_stream(t, pid, type); st->st_tb = (AVRational){1, 90000}; - if((v = htsmsg_get_str(c, "lang")) != NULL) + if((v = htsmsg_get_str(c, "language")) != NULL) av_strlcpy(st->st_lang, v, 4); if(!htsmsg_get_u32(c, "frameduration", &u32)) diff --git a/pvr.c b/pvr.c index 4f76864a..6dbcdedc 100644 --- a/pvr.c +++ b/pvr.c @@ -37,7 +37,6 @@ #include "tvhead.h" #include "channels.h" #include "subscriptions.h" -#include "htsclient.h" #include "pvr.h" #include "epg.h" #include "dispatch.h" @@ -438,7 +437,7 @@ pvr_database_load(void) if(channel != NULL && start && stop && title && status) { pvrr = calloc(1, sizeof(pvr_rec_t)); - pvrr->pvrr_channel = channel_find(channel, 1); + pvrr->pvrr_channel = channel_find_by_name(channel, 1); pvrr->pvrr_start = start; pvrr->pvrr_stop = stop; pvrr->pvrr_status = *status; diff --git a/rpc.c b/rpc.c index 3b592d0d..e60d1b3e 100644 --- a/rpc.c +++ b/rpc.c @@ -51,7 +51,7 @@ rpc_build_channel_msg(channel_t *ch) m = htsmsg_create(); htsmsg_add_str(m, "name", ch->ch_name); - htsmsg_add_u32(m, "tag", ch->ch_tag); + htsmsg_add_u32(m, "id", ch->ch_id); if(ch->ch_icon) htsmsg_add_str(m, "icon", ch->ch_icon); return m; @@ -148,7 +148,7 @@ rpc_channels_list(rpc_session_t *ses, htsmsg_t *in, void *opaque) out = htsmsg_create(); htsmsg_add_u32(out, "seq", ses->rs_seq); - RB_FOREACH(ch, &channel_tree, ch_global_link) + RB_FOREACH(ch, &channel_name_tree, ch_name_link) htsmsg_add_msg(out, "channel", rpc_build_channel_msg(ch)); return out; @@ -174,7 +174,7 @@ rpc_event_info(rpc_session_t *ses, htsmsg_t *in, void *opaque) if(htsmsg_get_u32(in, "tag", &u32) >= 0) { e = epg_event_find_by_tag(u32); } else if((s = htsmsg_get_str(in, "channel")) != NULL) { - if((ch = channel_find(s, 0)) == NULL) { + if((ch = channel_find_by_name(s, 0)) == NULL) { errtxt = "Channel not found"; } else { if(htsmsg_get_u32(in, "time", &u32) < 0) { diff --git a/rtsp.c b/rtsp.c index cfce733b..961f8055 100644 --- a/rtsp.c +++ b/rtsp.c @@ -104,7 +104,7 @@ rtsp_channel_by_url(char *url) return NULL; c++; - RB_FOREACH(ch, &channel_tree, ch_global_link) + RB_FOREACH(ch, &channel_name_tree, ch_name_link) if(!strcasecmp(ch->ch_sname, c)) return ch; diff --git a/serviceprobe.c b/serviceprobe.c index abec5f55..ff86e0b7 100644 --- a/serviceprobe.c +++ b/serviceprobe.c @@ -93,7 +93,7 @@ sp_packet_input(void *opaque, th_muxstream_t *tms, th_pkt_t *pkt) tvhlog(LOG_INFO, "serviceprobe", "Probed \"%s\" -- Ok", t->tht_svcname); if(t->tht_ch == NULL && t->tht_svcname != NULL) { - ch = channel_find(t->tht_svcname, 1); + ch = channel_find_by_name(t->tht_svcname, 1); transport_map_channel(t, ch); t->tht_config_change(t); diff --git a/transports.c b/transports.c index 8cf75bf8..af2f60d5 100644 --- a/transports.c +++ b/transports.c @@ -558,7 +558,7 @@ transport_map_channel(th_transport_t *t, channel_t *ch) if(ch == NULL) { if(t->tht_chname == NULL) return; - ch = channel_find(t->tht_chname, 1); + ch = channel_find_by_name(t->tht_chname, 1); } else { free(t->tht_chname); t->tht_chname = strdup(ch->ch_name); diff --git a/tvhead.h b/tvhead.h index b3eb0a37..f1fe7e4f 100644 --- a/tvhead.h +++ b/tvhead.h @@ -95,7 +95,7 @@ LIST_HEAD(autorec_list, autorec); extern time_t dispatch_clock; extern int startupcounter; extern struct th_transport_list all_transports; -extern struct channel_tree channel_tree; +extern struct channel_tree channel_name_tree; extern struct pvr_rec_list pvrr_global_list; extern struct th_subscription_list subscriptions; @@ -439,6 +439,8 @@ typedef struct th_transport { void (*tht_config_change)(struct th_transport *t); + const char *(*tht_networkname)(struct th_transport *t); + const char *(*tht_sourcename)(struct th_transport *t); int (*tht_quality_index)(struct th_transport *t); @@ -778,22 +780,19 @@ typedef struct tt_decoder { */ typedef struct channel { - RB_ENTRY(channel) ch_global_link; + RB_ENTRY(channel) ch_name_link; + char *ch_name; + char *ch_sname; + RB_ENTRY(channel) ch_identifier_link; + int ch_id; LIST_HEAD(, th_transport) ch_transports; LIST_HEAD(, th_subscription) ch_subscriptions; - int ch_index; - - const char *ch_name; - const char *ch_sname; - struct tt_decoder ch_tt; - int ch_tag; - enum { COMMERCIAL_DETECT_NONE, COMMERCIAL_DETECT_TTP192, diff --git a/webui/extjs.c b/webui/extjs.c index 0b6f1a04..a86ccb30 100644 --- a/webui/extjs.c +++ b/webui/extjs.c @@ -32,6 +32,8 @@ #include "webui.h" #include "access.h" #include "dtable.h" +#include "channels.h" +#include "psi.h" #include "dvb/dvb.h" #include "dvb/dvb_support.h" @@ -105,6 +107,7 @@ extjs_root(http_connection_t *hc, http_reply_t *hr, extjs_load(hq, "static/app/acleditor.js"); extjs_load(hq, "static/app/cwceditor.js"); extjs_load(hq, "static/app/dvb.js"); + extjs_load(hq, "static/app/chconf.js"); /** * Finally, the app itself @@ -199,6 +202,38 @@ extjs_tablemgr(http_connection_t *hc, http_reply_t *hr, return 0; } + +/** + * + */ +static int +extjs_chlist(http_connection_t *hc, http_reply_t *hr, + const char *remain, void *opaque) +{ + htsbuf_queue_t *hq = &hr->hr_q; + htsmsg_t *out, *array, *c; + channel_t *ch; + + out = htsmsg_create(); + + array = htsmsg_create_array(); + + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { + c = htsmsg_create(); + htsmsg_add_str(c, "name", ch->ch_name); + htsmsg_add_u32(c, "chid", ch->ch_id); + htsmsg_add_msg(array, NULL, c); + } + + htsmsg_add_msg(out, "entries", array); + + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + http_output(hc, hr, "text/x-json; charset=UTF-8", NULL, 0); + return 0; +} + + /** * */ @@ -328,7 +363,7 @@ json_single_record(htsmsg_t *rec, const char *root) array = htsmsg_create_array(); htsmsg_add_msg(array, NULL, rec); - htsmsg_add_msg(out, "dvbadapters", array); + htsmsg_add_msg(out, root, array); return out; } @@ -399,6 +434,188 @@ extjs_dvbadapter(http_connection_t *hc, http_reply_t *hr, return 0; } +/** + * + */ +static htsmsg_t * +build_transport_msg(th_transport_t *t) +{ + htsmsg_t *r = htsmsg_create(); + th_stream_t *st; + + char video[200]; + char audio[200]; + char subtitles[200]; + char scrambling[200]; + + htsmsg_add_u32(r, "enabled", !t->tht_disabled); + htsmsg_add_str(r, "name", t->tht_svcname); + + htsmsg_add_str(r, "provider", t->tht_provider ?: ""); + htsmsg_add_str(r, "network", t->tht_networkname(t)); + htsmsg_add_str(r, "source", t->tht_sourcename(t)); + + htsmsg_add_str(r, "status", transport_status_to_text(t->tht_last_status)); + + video[0] = 0; + audio[0] = 0; + subtitles[0] = 0; + scrambling[0] = 0; + + LIST_FOREACH(st, &t->tht_streams, st_link) { + switch(st->st_type) { + case HTSTV_TELETEXT: + case HTSTV_SUBTITLES: + case HTSTV_PAT: + case HTSTV_PMT: + break; + + case HTSTV_MPEG2VIDEO: + snprintf(video + strlen(video), sizeof(video) - strlen(video), + "%sMPEG-2 (PID:%d", strlen(video) > 0 ? ", " : "", + st->st_pid); + + video: + if(st->st_frame_duration) { + snprintf(video + strlen(video), sizeof(video) - strlen(video), + ", %d Hz)", 90000 / st->st_frame_duration); + } else { + snprintf(video + strlen(video), sizeof(video) - strlen(video), + ")"); + } + + break; + + case HTSTV_H264: + snprintf(video + strlen(video), sizeof(video) - strlen(video), + "%sH.264 (PID:%d", strlen(video) > 0 ? ", " : "", + st->st_pid); + goto video; + + case HTSTV_MPEG2AUDIO: + snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio), + "%sMPEG-2 (PID:%d", strlen(audio) > 0 ? ", " : "", + st->st_pid); + audio: + if(st->st_lang[0]) { + snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio), + ", languange: \"%s\")", st->st_lang); + } else { + snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio), + ")"); + } + break; + + case HTSTV_AC3: + snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio), + "%sAC3 (PID:%d", strlen(audio) > 0 ? ", " : "", + st->st_pid); + goto audio; + + case HTSTV_CA: + snprintf(scrambling + strlen(scrambling), + sizeof(scrambling) - strlen(scrambling), + "%s%s", strlen(scrambling) > 0 ? ", " : "", + psi_caid2name(st->st_caid)); + break; + } + } + + htsmsg_add_str(r, "video", video); + htsmsg_add_str(r, "audio", audio); + htsmsg_add_str(r, "scrambling", scrambling[0] ? scrambling : "none"); + return r; +} + + +/** + * + */ +static int +extjs_channel(http_connection_t *hc, http_reply_t *hr, + const char *remain, void *opaque) +{ + htsbuf_queue_t *hq = &hr->hr_q; + const char *s = http_arg_get(&hc->hc_req_args, "chid"); + const char *op = http_arg_get(&hc->hc_req_args, "op"); + channel_t *ch = s ? channel_find_by_identifier(atoi(s)) : NULL; + channel_t *ch2; + th_transport_t *t; + int reloadchlist = 0; + htsmsg_t *out, *array, *r; + + if(ch == NULL) + return HTTP_STATUS_BAD_REQUEST; + + if(!strcmp(op, "load")) { + r = htsmsg_create(); + htsmsg_add_u32(r, "id", ch->ch_id); + htsmsg_add_str(r, "name", ch->ch_name); + htsmsg_add_str(r, "comdetect", "tt192"); + out = json_single_record(r, "channels"); + + } else if(!strcmp(op, "gettransports")) { + out = htsmsg_create(); + array = htsmsg_create_array(); + LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) + htsmsg_add_msg(array, NULL, build_transport_msg(t)); + + htsmsg_add_msg(out, "entries", array); + + } else if(!strcmp(op, "delete")) { + + channel_delete(ch); + + out = htsmsg_create(); + htsmsg_add_u32(out, "reloadchlist", 1); + htsmsg_add_u32(out, "success", 1); + + } else if(!strcmp(op, "mergefrom")) { + + if((s = http_arg_get(&hc->hc_req_args, "srcch")) == NULL) + return HTTP_STATUS_BAD_REQUEST; + + ch2 = channel_find_by_identifier(atoi(s)); + if(ch2 == NULL || ch2 == ch) + return HTTP_STATUS_BAD_REQUEST; + + channel_merge(ch, ch2); /* ch2 goes away here */ + + out = htsmsg_create(); + htsmsg_add_u32(out, "reloadchlist", 1); + htsmsg_add_u32(out, "success", 1); + + + } else if(!strcmp(op, "save")) { + + if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL && + strcmp(s, ch->ch_name)) { + + if(channel_rename(ch, s)) { + out = htsmsg_create(); + htsmsg_add_u32(out, "success", 0); + htsmsg_add_str(out, "errormsg", "Channel name already exist"); + goto response; + } else { + reloadchlist = 1; + } + } + + out = htsmsg_create(); + htsmsg_add_u32(out, "reloadchlist", 1); + htsmsg_add_u32(out, "success", 1); + + } else { + return HTTP_STATUS_BAD_REQUEST; + } + response: + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + + http_output(hc, hr, "text/x-json; charset=UTF-8", NULL, 0); + return 0; +} + /** * WEB user interface */ @@ -410,4 +627,6 @@ extjs_start(void) http_path_add("/dvbtree", NULL, extjs_dvbtree, ACCESS_WEB_INTERFACE); http_path_add("/dvbadapter", NULL, extjs_dvbadapter, ACCESS_WEB_INTERFACE); http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE); + http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE); + http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE); } diff --git a/webui/static/app/chconf.js b/webui/static/app/chconf.js new file mode 100644 index 00000000..03d4d279 --- /dev/null +++ b/webui/static/app/chconf.js @@ -0,0 +1,361 @@ +/** + * Channel details + */ +tvheadend.channeldetails = function(chid, chname) { + var fm = Ext.form; + var xg = Ext.grid; + + var expander = new xg.RowExpander({ + tpl : new Ext.Template( + '
Video:{video}
', + '
Audio:{audio}
', + '
Subtitling:{subtitles}
', + '
Scrambling:{scrambling}
' + ) + }); + + var enabledColumn = new Ext.grid.CheckColumn({ + header: "Enabled", + dataIndex: 'enabled', + width: 60 + }); + + var cm = new Ext.grid.ColumnModel([expander, + enabledColumn, + { + width: 125, + id:'name', + header: "Original name", + dataIndex: 'name', + },{ + width: 125, + id:'status', + header: "Last status", + dataIndex: 'status', + },{ + width: 125, + id:'provider', + header: "Provider", + dataIndex: 'provider', + },{ + width: 125, + id:'network', + header: "Network", + dataIndex: 'network', + },{ + width: 250, + id:'source', + header: "Source", + dataIndex: 'source', + } + ]); + + + var transportRecord = Ext.data.Record.create([ + {name: 'enabled'}, + {name: 'status'}, + {name: 'name'}, + {name: 'provider'}, + {name: 'network'}, + {name: 'source'}, + {name: 'video'}, + {name: 'audio'}, + {name: 'scrambling'}, + {name: 'subtitles'} + ]); + + var transportsstore = + new Ext.data.JsonStore({root: 'entries', + fields: transportRecord, + url: "channel", + autoLoad: true, + id: 'id', + storeid: 'id', + baseParams: {chid: chid, op: "gettransports"} + }); + + + var transportsgrid = new Ext.grid.EditorGridPanel({ + title:'Transports', + anchor: '100% 50%', + plugins:[enabledColumn, expander], + store: transportsstore, + clicksToEdit: 2, + cm: cm, + selModel: new Ext.grid.RowSelectionModel({singleSelect:false}) + }); + + + var confreader = new Ext.data.JsonReader({ + root: 'channels', + }, ['name', 'comdetect']); + + var confpanel = new Ext.FormPanel({ + frame:true, + disabled:true, + anchor: '100% 50%', + labelAlign: 'right', + labelWidth: 150, + waitMsgTarget: true, + reader: confreader, + // defaultType: 'textfield', + + items: [{ + layout:'column', + items:[{ + columnWidth:.5, + layout: 'form', + defaultType: 'textfield', + items: [{ + + fieldLabel: 'Channel name', + name: 'name', + } + /* + , + new Ext.form.ComboBox({ + allowBlank: false, + fieldLabel: 'Commercial detection', + name: 'comdetect', + displayField:'mode', + valueField:'imode', + mode: 'local', + triggerAction: 'all', + selectOnFocus:true, + editable:false, + store: new Ext.data.SimpleStore({ + fields: ['imode', 'mode'], + data: [ + ['none', 'None'], + ['tt192', 'Teletext page 192']] + }) + }) + */ + ] + } + /* + ,{ + columnWidth:.5, + layout: 'form', + items: [{ + xtype: 'checkboxgroup', + fieldLabel: 'Tags', + itemCls: 'x-check-group-alt', + columns: 1, + vertical: true, + items: [{ + boxLabel: 'Favourites', name: 'favourite'},{ + boxLabel: 'Sports', name: 'sports'},{ + boxLabel: 'News', name: 'news'},{ + boxLabel: 'Movies', name: 'movies'},{ + boxLabel: 'Children', name: 'children'} + ] + } + ] + } */ + ] + }] + }); + + confpanel.getForm().load({url:'/channel', + params:{'chid': chid, 'op':'load'}, + success:function(form, action) { + confpanel.enable(); + }}); + + + function saveChanges() { + confpanel.getForm().submit({url:'/channel', + params:{'chid': chid, 'op':'save'}, + waitMsg:'Saving Data...', + + success: function(form, action) { + + if(action.result.reloadchlist) { + tvheadend.chconfliststore.reload(); + } + }, + + failure: function(form, action) { + Ext.Msg.alert('Save failed', action.result.errormsg); + } + }); + } + + function deleteChannel() { + Ext.MessageBox.confirm('Message', + 'Do you really want to delete "' + chname + '"', + function(button) { + if(button == 'no') + return; + Ext.Ajax.request({url: '/channel', + params:{'chid': chid, 'op':'delete'}, + success: function() { + tvheadend.chconfliststore.reload(); + panel.destroy(); + } + }); + } + ); + } + + var panel = new Ext.Panel({ + title: chname, + border:false, + tbar: [{ + text: "Save changes", + handler: saveChanges + },{ + text: "Delete channel", + handler: deleteChannel + }], + defaults: { + border:false, + }, + layout:'anchor', + items: [confpanel,transportsgrid] + }); + + + panel.on('afterlayout', function(parent, n) { + var DropTargetEl = parent.body.dom; + + var DropTarget = new Ext.dd.DropTarget(DropTargetEl, { + ddGroup : 'chconfddgroup', + notifyEnter : function(ddSource, e, data) { + + //Add some flare to invite drop. + parent.body.stopFx(); + parent.body.highlight(); + }, + notifyDrop : function(ddSource, e, data){ + + // Reference the record (single selection) for readability + var selectedRecord = ddSource.dragData.selections[0]; + + Ext.MessageBox.confirm('Merge channels', + 'Copy transport configuration from "' + selectedRecord.data.name + + '" to "' + chname + '". This will also remove the channel "' + + selectedRecord.data.name + '"', + function(button) { + if(button == 'no') + return; + Ext.Ajax.request({url: '/channel', + params:{chid: chid, + op:'mergefrom', + srcch: selectedRecord.data.chid}, + success: function() { + transportsstore.reload(); + tvheadend.chconfliststore.reload(); + }}); + } + ); + } + }); + }); + return panel; + } + + +/** + * + */ +tvheadend.chconf = function() { + + var ChannelRecord = Ext.data.Record.create([ + {name: 'name'}, + {name: 'chid'}]); + + var store = new Ext.data.JsonStore({root: 'entries', + fields: ChannelRecord, + url: "chlist", + autoLoad: true, + id: 'id', + storeid: 'id' + }); + + + var chlist = new Ext.grid.GridPanel({ + ddGroup: 'chconfddgroup', + enableDragDrop: true, + stripeRows:true, + region:'west', + width: 300, + columns: [{id:'name', + header: "Channel name", + width: 260, + dataIndex: 'name'} + ], + selModel: new Ext.grid.RowSelectionModel({singleSelect:true}), + store: store, + }); + + var details = new Ext.Panel({ + region:'center', layout:'fit', + items:[{border: false}] + }); + + + var panel = new Ext.Panel({ + listeners: {activate: handleActivate}, + border: false, + title:'Channels', + layout:'border', + items: [chlist, details] + }); + + function handleActivate(tab){ + store.reload(); + } + + chlist.on('rowclick', function(grid, n) { + var rec = store.getAt(n); + + details.remove(details.getComponent(0)); + details.doLayout(); + + var newpanel = new tvheadend.channeldetails(rec.data.chid, + rec.data.name); + + details.add(newpanel); + details.doLayout(); + }); + + + /** + * Setup Drop Targets + */ + + // This will make sure we only drop to the view container + + /* + var DropTargetEl = details.getView(); + + var DropTarget = new Ext.dd.DropTarget(DropTargetEl, { + ddGroup : 'chconfddgroup', + notifyEnter : function(ddSource, e, data) { + + //Add some flare to invite drop. + panel.body.stopFx(); + panel.body.highlight(); + }, + notifyDrop : function(ddSource, e, data){ + + // Reference the record (single selection) for readability + var selectedRecord = ddSource.dragData.selections[0]; + + console.log(selectedRecord); + } + }); + + */ + /* + details.on('afterlayout', function(parent, n) { + console.log(parent); + }); + */ + + tvheadend.chconfliststore = store; + + return panel; +} \ No newline at end of file diff --git a/webui/static/app/dvb.js b/webui/static/app/dvb.js index 6ffcd529..7ec80527 100644 --- a/webui/static/app/dvb.js +++ b/webui/static/app/dvb.js @@ -167,6 +167,7 @@ tvheadend.dvb_adapterdetails = function(adapterId, adapterName, treenode) { border: false, items:[status, confpanel], tbar:[{ + iconCls:'add', text: 'Add mux(es)', handler: addmux },{ diff --git a/webui/static/app/extensions.js b/webui/static/app/extensions.js index 52beba69..4baf3ae3 100644 --- a/webui/static/app/extensions.js +++ b/webui/static/app/extensions.js @@ -143,3 +143,135 @@ Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { } } }); + + + + +Ext.grid.RowExpander = function(config){ + Ext.apply(this, config); + + this.addEvents({ + beforeexpand : true, + expand: true, + beforecollapse: true, + collapse: true + }); + + Ext.grid.RowExpander.superclass.constructor.call(this); + + if(this.tpl){ + if(typeof this.tpl == 'string'){ + this.tpl = new Ext.Template(this.tpl); + } + this.tpl.compile(); + } + + this.state = {}; + this.bodyContent = {}; +}; + +Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, { + header: "", + width: 20, + sortable: false, + fixed:true, + menuDisabled:true, + dataIndex: '', + id: 'expander', + lazyRender : true, + enableCaching: true, + + getRowClass : function(record, rowIndex, p, ds){ + p.cols = p.cols-1; + var content = this.bodyContent[record.id]; + if(!content && !this.lazyRender){ + content = this.getBodyContent(record, rowIndex); + } + if(content){ + p.body = content; + } + return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed'; + }, + + init : function(grid){ + this.grid = grid; + + var view = grid.getView(); + view.getRowClass = this.getRowClass.createDelegate(this); + + view.enableRowBody = true; + + grid.on('render', function(){ + view.mainBody.on('mousedown', this.onMouseDown, this); + }, this); + }, + + getBodyContent : function(record, index){ + if(!this.enableCaching){ + return this.tpl.apply(record.data); + } + var content = this.bodyContent[record.id]; + if(!content){ + content = this.tpl.apply(record.data); + this.bodyContent[record.id] = content; + } + return content; + }, + + onMouseDown : function(e, t){ + if(t.className == 'x-grid3-row-expander'){ + e.stopEvent(); + var row = e.getTarget('.x-grid3-row'); + this.toggleRow(row); + } + }, + + renderer : function(v, p, record){ + p.cellAttr = 'rowspan="2"'; + return '
 
'; + }, + + beforeExpand : function(record, body, rowIndex){ + if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){ + if(this.tpl && this.lazyRender){ + body.innerHTML = this.getBodyContent(record, rowIndex); + } + return true; + }else{ + return false; + } + }, + + toggleRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row); + }, + + expandRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + if(this.beforeExpand(record, body, row.rowIndex)){ + this.state[record.id] = true; + Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); + this.fireEvent('expand', this, record, body, row.rowIndex); + } + }, + + collapseRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); + if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){ + this.state[record.id] = false; + Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); + this.fireEvent('collapse', this, record, body, row.rowIndex); + } + } +}); diff --git a/webui/static/app/tvheadend.js b/webui/static/app/tvheadend.js index b086d175..cd99ad29 100644 --- a/webui/static/app/tvheadend.js +++ b/webui/static/app/tvheadend.js @@ -87,7 +87,8 @@ tvheadend.app = function() { activeTab:0, autoScroll:true, title: 'Configuration', - items: [new tvheadend.dvb, + items: [new tvheadend.chconf, + new tvheadend.dvb, new tvheadend.acleditor, new tvheadend.cwceditor] });