]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add channel configuration and setup.
authorAndreas Öman <andreas@lonelycoder.com>
Tue, 26 Aug 2008 14:15:33 +0000 (14:15 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Tue, 26 Aug 2008 14:15:33 +0000 (14:15 +0000)
htsclient.c is now no longer supported.

28 files changed:
Makefile
autorec.c
avgen.c
channels.c
channels.h
dvb/dvb_transport.c
epg.c
epg_xmltv.c
ffmuxer.c
file_input.c
htsclient.c [deleted file]
htsclient.h [deleted file]
htsp.c
htsp_muxer.c
iptv_output.c
main.c
psi.c
pvr.c
rpc.c
rtsp.c
serviceprobe.c
transports.c
tvhead.h
webui/extjs.c
webui/static/app/chconf.js [new file with mode: 0644]
webui/static/app/dvb.js
webui/static/app/extensions.js
webui/static/app/tvheadend.js

index ac713addd6e8c8726238d1bbc5b439793bfd0fad..f3699845436f1bf3247f04cf5fa23defaaeb7e4c 100644 (file)
--- 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
 
index b8a8fbbc63137780f458aea0666c1fd0e994c70d..43a8bbf7311356cf1fb4f44e899183afcd9f5210 100644 (file)
--- 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 b0020ed8c42ca4d415c83e7de46ddc147e7a0322..edc0bd74e9f4bb145c0c0b0f88daf1a2d818633e 100644 (file)
--- 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));
 
index 9670435e66c148831c8ee67e8fcc91a2aeec301b..4823ce62010d0dde6ec100b5da07d69316a5418c 100644 (file)
@@ -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,167 +126,87 @@ 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);
 }
 
+
+
 /**
  *
  */
-channel_t *
-channel_find(const char *name, int create)
+static channel_t *
+channel_create(const char *name)
 {
-  channel_t *ch, skel;
-
-  skel.ch_name = name;
-
-  if((ch = RB_FIND(&channel_tree, &skel,  ch_global_link, channelcmp)) != NULL)
-    return ch;
-
-  if(create == 0)
-    return NULL;
+  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));
-  ch->ch_index = channel_tree.entries;
-
   TAILQ_INIT(&ch->ch_epg_events);
 
   channel_set_name(ch, name);
 
-  ch->ch_tag = tag_get();
+  ch->ch_id = id;
+  x = RB_INSERT_SORTED(&channel_identifier_tree, ch, 
+                      ch_identifier_link, chidcmp);
+  assert(x == NULL);
   return ch;
 }
 
-
-static struct strtab commercial_detect_tab[] = {
-  { "none",       COMMERCIAL_DETECT_NONE   },
-  { "ttp192",     COMMERCIAL_DETECT_TTP192 },
-};
-
-
 /**
  *
  */
-void
-channels_load(void)
+channel_t *
+channel_find_by_name(const char *name, int create)
 {
-#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
+  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);
 }
 
 
-
 /**
- * The index stuff should go away
+ *
  */
 channel_t *
-channel_by_index(uint32_t index)
+channel_find_by_identifier(int id)
 {
-  channel_t *ch;
+  channel_t skel, *ch;
+  skel.ch_id = id;
+  ch = RB_FIND(&channel_identifier_tree, &skel, ch_identifier_link, chidcmp);
+  return ch;
+}
+
 
-  RB_FOREACH(ch, &channel_tree, ch_global_link)
-    if(ch->ch_index == index)
-      return ch;
 
-  return NULL;
-}
 
+static struct strtab commercial_detect_tab[] = {
+  { "none",       COMMERCIAL_DETECT_NONE   },
+  { "ttp192",     COMMERCIAL_DETECT_TTP192 },
+};
 
 
 /**
  *
  */
-channel_t *
-channel_by_tag(uint32_t tag)
+void
+channels_load(void)
 {
-  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);
index f2106c83d695194528155b308eec5ee0ebb98993..08d514afeacd8608343a4464fda1c8dd7b3f077f 100644 (file)
 
 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);
index d9da18a0135926797e882c58fa5655e544b8f2bc..04741fdf745893d9c1b02f5ab40c7be7b56580ef 100644 (file)
@@ -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;
+
+  return tdmi->tdmi_adapter->tda_displayname;
+}
 
-  tdmi = t->tht_dvb_mux_instance;
 
-  snprintf(buf, sizeof(buf), "\"%s\" on \"%s\"",
-          tdmi->tdmi_network ?: "Unknown network",
-          tdmi->tdmi_adapter->tda_rootpath);
+/**
+ * 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 buf;
+  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 9f2f8494ae27692a67b49bbe65af675dd67ffcfe..139a28368a0e8f5bdddcd92271e44b403d67e5b3 100644 (file)
--- 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;
 
index 2dcb07b40c10dcdbc3f86d11afc247c817860f93..27aef9104f3baefa3f69627b30f2225a805e995e 100644 (file)
@@ -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);
       
index ab65ec5cf93b2b04ce2fff33ade99e0bf9f6d4af..1bc5a85517483e8295ebe6d0bef3b2f0a7aa164b 100644 (file)
--- 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"
 
index 39f1121f4394e00d4118c7f13d2c2074b0a65b4d..83a7a67ed2ea33541cecc712f41e3cb909c8b059 100644 (file)
@@ -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 (file)
index 3f0ebd9..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- */
-
-#include <assert.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-
-#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 (file)
index 6d5ed22..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- */
-
-#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 76c3a2e27c8ac44c9c6c2777ad83cc046aee7f74..495cd00f9873ddee8cc4112ea10a0c1a0e305573 100644 (file)
--- 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) {
index 8a2c801eec6467ec0562962001e3a0e6344204ce..a8de63715410200ccb17ef7c0b170efd6f7d1994 100644 (file)
@@ -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;
   }
 
index 39b8d80e503f200e99c95cb3ca204b606debbc83..c55c662cfe3258e32d9f61e9fe0002d0285a0437 100644 (file)
@@ -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 3cf2433beba730716be024718ba3f9040e82edc1..2378b150ba3875a2f3cdfdc479b23f16207fed10 100644 (file)
--- 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 790ae90c6a453c9a2f236fc5f188dfc1e852338a..c991428afece9b9c8c850bb49beb267aa91587e1 100644 (file)
--- 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 4f76864a2c9d423a43eed7103965b519cd59de61..6dbcdedcd21c578a8edd71daf632a595e77e2520 100644 (file)
--- 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 3b592d0d105003650f29dd7e1976f1baf26f0330..e60d1b3eb7c3cf5ad4ff58d62dd027063b165a4b 100644 (file)
--- 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 cfce733bf064ca15e7246bc1e638bf77ec03ab18..961f805580aa724384c9c24df0b2fd0af46d8a00 100644 (file)
--- 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;
 
index abec5f5526962e0f654f7ec949c5f69390990d73..ff86e0b7f03e79777e897b774a1d31a1f1bb0722 100644 (file)
@@ -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);
index 8cf75bf8fc82ae9a38353906271c2c48070d3e79..af2f60d550ea13f4b9fd9f416aa4626f95f1ad01 100644 (file)
@@ -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);
index b3eb0a3799d050e759ea7d1a3a5a2b733d28a411..f1fe7e4fc78e4a51f3cbed938ba5e5a4e6586453 100644 (file)
--- 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,
index 0b6f1a043aa08817a0cc9c0faa60b4e284d3c6ed..a86ccb301af23640a7e8f961ea77a603ba3fbe41 100644 (file)
@@ -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 (file)
index 0000000..03d4d27
--- /dev/null
@@ -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(
+            '<div><b width=100px>Video:</b>{video}</div>',
+            '<div><b>Audio:</b>{audio}</div>',
+            '<div><b>Subtitling:</b>{subtitles}</div>',
+            '<div><b>Scrambling:</b>{scrambling}</div>'
+        )
+    });
+  
+    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
index 6ffcd529aca6092771ebcf1dea2b9ebcf5f4af06..7ec8052769ce7dbec727e548d730ee00647a09f2 100644 (file)
@@ -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
                },{
index 52beba69ff3a2dae171a7f3c43871fdc033397c4..4baf3ae37b819e3734864016b9f07f06db2bacce 100644 (file)
@@ -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 '<div class="x-grid3-row-expander">&#160;</div>';
+    },
+
+    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);
+        }
+    }
+});
index b086d175a7aa7c9060251e903259d7288211b908..cd99ad291634b31ea9e81637ffdac28f1d934fa2 100644 (file)
@@ -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]
                });