]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
bouquet: add enigma2 parser, fixes #2994
authorJaroslav Kysela <perex@perex.cz>
Mon, 19 Oct 2015 16:46:48 +0000 (18:46 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 19 Oct 2015 16:46:48 +0000 (18:46 +0200)
src/api/api_bouquet.c
src/bouquet.c
src/bouquet.h
src/htsbuf.c
src/input/mpegts.h
src/input/mpegts/dvb_psi.c
src/input/mpegts/iptv/iptv.c
src/input/mpegts/mpegts_service.c
src/webui/static/app/cteditor.js

index 93f914a986ec501025b432d3efd4421060d32f89..d5ad9eb614888b221b0ab4fa69f777d4a0b6b1fd 100644 (file)
@@ -64,7 +64,7 @@ api_bouquet_create
   htsmsg_t *conf;
   bouquet_t *bq;
 
-  if (!(conf  = htsmsg_get_map(args, "conf")))
+  if (!(conf = htsmsg_get_map(args, "conf")))
     return EINVAL;
 
   pthread_mutex_lock(&global_lock);
index 7b101d3e6ae1c299174fcf6babacc3830182b54f..ebd3c60b82c2aec22bfa066b93529990bf6dd07f 100644 (file)
 #include "service.h"
 #include "channels.h"
 #include "service_mapper.h"
+#include "download.h"
+
+typedef struct bouquet_download {
+  bouquet_t  *bq;
+  download_t  download;
+  gtimer_t    timer;
+} bouquet_download_t;
 
 bouquet_tree_t bouquets;
 
 static uint64_t bouquet_get_channel_number0(bouquet_t *bq, service_t *t);
+static void bouquet_download_trigger(bouquet_t *bq);
+static void bouquet_download_stop(void *aux);
+static int bouquet_download_process(void *aux, const char *last_url, char *data, size_t len);
 
 /**
  *
@@ -45,6 +55,8 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
                const char *name, const char *src)
 {
   bouquet_t *bq, *bq2;
+  bouquet_download_t *bqd;
+  char buf[128];
   int i;
 
   lock_assert(&global_lock);
@@ -52,6 +64,7 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
   bq = calloc(1, sizeof(bouquet_t));
   bq->bq_services = idnode_set_create(1);
   bq->bq_active_services = idnode_set_create(1);
+  bq->bq_ext_url_period = 60;
 
   if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
     if (uuid)
@@ -78,11 +91,26 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
     bq->bq_src = strdup(src);
   }
 
+  if (bq->bq_ext_url) {
+    free(bq->bq_src);
+    snprintf(buf, sizeof(buf), "exturl://%s", idnode_uuid_as_sstr(&bq->bq_id));
+    bq->bq_src = strdup(buf);
+    bq->bq_download = bqd = calloc(1, sizeof(*bqd));
+    bqd->bq = bq;
+    download_init(&bqd->download, "bouquet");
+    bqd->download.process = bouquet_download_process;
+    bqd->download.stop = bouquet_download_stop;
+    bouquet_change_comment(bq, bq->bq_ext_url, 0);
+  }
+
   bq2 = RB_INSERT_SORTED(&bouquets, bq, bq_link, _bq_cmp);
   assert(bq2 == NULL);
 
   bq->bq_saveflag = 1;
 
+  if (bq->bq_ext_url)
+    bouquet_download_trigger(bq);
+
   return bq;
 }
 
@@ -92,17 +120,25 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
 static void
 bouquet_destroy(bouquet_t *bq)
 {
+  bouquet_download_t *bqd;
+
   if (!bq)
     return;
 
   RB_REMOVE(&bouquets, bq, bq_link);
   idnode_unlink(&bq->bq_id);
 
+  if ((bqd = bq->bq_download) != NULL) {
+    download_done(&bqd->download);
+    free(bqd);
+  }
+
   idnode_set_free(bq->bq_active_services);
   idnode_set_free(bq->bq_services);
   assert(bq->bq_services_waiting == NULL);
   free((char *)bq->bq_chtag_waiting);
   free(bq->bq_name);
+  free(bq->bq_ext_url);
   free(bq->bq_src);
   free(bq->bq_comment);
   free(bq);
@@ -254,7 +290,7 @@ bouquet_map_channel(bouquet_t *bq, service_t *t)
  *
  */
 void
-bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, uint32_t tag)
+bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag)
 {
   service_lcn_t *tl;
   idnode_list_mapping_t *ilm;
@@ -494,8 +530,8 @@ bouquet_get_channel_number(bouquet_t *bq, service_t *t)
 /*
  *
  */
-static uint32_t
-bouquet_get_tag_number(bouquet_t *bq, service_t *t)
+static const char *
+bouquet_get_tag_name(bouquet_t *bq, service_t *t)
 {
   return 0;
 }
@@ -763,7 +799,7 @@ bouquet_class_services_get ( void *obj )
   bouquet_t *bq = obj;
   service_t *t;
   int64_t lcn;
-  uint32_t tag;
+  const char *tag;
   size_t z;
 
   /* Add all */
@@ -772,8 +808,8 @@ bouquet_class_services_get ( void *obj )
     e = htsmsg_create_map();
     if ((lcn = bouquet_get_channel_number0(bq, t)) != 0)
       htsmsg_add_s64(e, "lcn", lcn);
-    if ((tag = bouquet_get_tag_number(bq, t)) != 0)
-      htsmsg_add_s64(e, "tag", lcn);
+    if ((tag = bouquet_get_tag_name(bq, t)) != NULL)
+      htsmsg_add_str(e, "tag", tag);
     htsmsg_add_msg(m, idnode_uuid_as_sstr(&t->s_id), e);
   }
 
@@ -813,6 +849,12 @@ bouquet_class_services_count_get ( void *obj )
   return &u32;
 }
 
+static void
+bouquet_class_ext_url_notify ( void *obj, const char *lang )
+{
+  bouquet_download_trigger((bouquet_t *)obj);
+}
+
 const idclass_t bouquet_class = {
   .ic_class      = "bouquet",
   .ic_caption    = N_("Bouquet"),
@@ -887,6 +929,30 @@ const idclass_t bouquet_class = {
       .name     = N_("Name"),
       .off      = offsetof(bouquet_t, bq_name),
     },
+    {
+      .type     = PT_STR,
+      .id       = "ext_url",
+      .name     = N_("External URL"),
+      .off      = offsetof(bouquet_t, bq_ext_url),
+      .opts     = PO_HIDDEN | PO_MULTILINE,
+      .notify   = bouquet_class_ext_url_notify,
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "ssl_peer_verify",
+      .name     = N_("ssl_peer_verify"),
+      .off      = offsetof(bouquet_t, bq_ssl_peer_verify),
+      .opts     = PO_ADVANCED | PO_HIDDEN,
+      .notify   = bouquet_class_ext_url_notify,
+    },
+    {
+      .type     = PT_U32,
+      .id       = "ext_url_period",
+      .name     = N_("Re-fetch period (mins)"),
+      .off      = offsetof(bouquet_t, bq_ext_url_period),
+      .opts     = PO_ADVANCED | PO_HIDDEN,
+      .def.i    = 60
+    },
     {
       .type     = PT_STR,
       .id       = "source",
@@ -935,6 +1001,100 @@ const idclass_t bouquet_class = {
   }
 };
 
+/**
+ *
+ */
+static void
+bouquet_download_trigger0(void *aux)
+{
+  bouquet_download_t *bqd = aux;
+  bouquet_t *bq = bqd->bq;
+
+  download_start(&bqd->download, bq->bq_ext_url, bqd);
+  gtimer_arm(&bqd->timer, bouquet_download_trigger0, bqd,
+             MAX(1, bq->bq_ext_url_period) * 60);
+}
+
+static void
+bouquet_download_trigger(bouquet_t *bq)
+{
+  bouquet_download_t *bqd = bq->bq_download;
+  if (bqd) {
+    bqd->download.ssl_peer_verify = bq->bq_ssl_peer_verify;
+    bouquet_download_trigger0(bqd);
+  }
+}
+
+static void
+bouquet_download_stop(void *aux)
+{
+  bouquet_download_t *bqd = aux;
+  gtimer_disarm(&bqd->timer);
+}
+
+static int
+parse_enigma2(bouquet_t *bq, char *data)
+{
+  extern service_t *mpegts_service_find_e2(uint32_t stype, uint32_t sid,
+                                           uint32_t tsid, uint32_t onid,
+                                           uint32_t hash);
+  char *argv[11], *p, *tagname = NULL, *name;
+  long lv, stype, sid, tsid, onid, hash;
+  uint32_t seen = 0;
+  int n;
+
+  while (*data) {
+    if (strncmp(data, "#NAME ", 6) == 0) {
+      for (data += 6, p = data; *data && *data != '\r' && *data != '\n'; data++);
+      if (*data) { *data = '\0'; data++; }
+      if (bq->bq_name == NULL || bq->bq_name[0] == '\0')
+        bq->bq_name = strdup(p);
+    } if (strncmp(data, "#SERVICE ", 9) == 0) {
+      data += 9, p = data;
+      while (*data && *data != '\r' && *data != '\n') data++;
+      if (*data) { *data = '\0'; data++; }
+      n = http_tokenize(p, argv, ARRAY_SIZE(argv), ':');
+      if (n >= 10) {
+        if (strtol(argv[0], NULL, 0) != 1) goto next;  /* item type */
+        lv = strtol(argv[1], NULL, 16);                /* service flags? */
+        if (lv != 0 && lv != 0x64) goto next;
+        stype = strtol(argv[2], NULL, 16);             /* DVB service type */
+        sid   = strtol(argv[3], NULL, 16);             /* DVB service ID */
+        tsid  = strtol(argv[4], NULL, 16);
+        onid  = strtol(argv[5], NULL, 16);
+        hash  = strtol(argv[6], NULL, 16);
+        name  = n > 10 ? argv[10] : NULL;
+        if (lv == 0) {
+          service_t *s = mpegts_service_find_e2(stype, sid, tsid, onid, hash);
+          if (s)
+            bouquet_add_service(bq, s, ++seen, tagname);
+        } else if (lv == 64) {
+          tagname = name;
+        }
+      }
+    } else {
+      while (*data && *data != '\r' && *data != '\n') data++;
+    }
+next:
+    while (*data && (*data == '\r' || *data == '\n')) data++;
+  }
+  bouquet_completed(bq, seen);
+  tvhinfo("bouquet", "parsed Enigma2 bouquet %s (%d services)", bq->bq_name, seen);
+  return 0;
+}
+
+static int
+bouquet_download_process(void *aux, const char *last_url, char *data, size_t len)
+{
+  bouquet_download_t *bqd = aux;
+  while (*data) {
+    while (*data && *data < ' ') data++;
+    if (*data && !strncmp(data, "#NAME ", 6))
+      return parse_enigma2(bqd->bq, data);
+  }
+  return 0;
+}
+
 /**
  *
  */
@@ -966,7 +1126,7 @@ bouquet_service_resolve(void)
   htsmsg_field_t *f;
   service_t *s;
   int64_t lcn;
-  uint32_t tag;
+  const char *tag;
   int saveflag;
 
   lock_assert(&global_lock);
@@ -979,7 +1139,7 @@ bouquet_service_resolve(void)
       HTSMSG_FOREACH(f, bq->bq_services_waiting) {
         if ((e = htsmsg_field_get_map(f)) == NULL) continue;
         lcn = htsmsg_get_s64_or_default(e, "lcn", 0);
-        tag = htsmsg_get_u32_or_default(e, "tag", 0);
+        tag = htsmsg_get_str(e, "tag");
         s = service_find_by_identifier(f->hmf_name);
         if (s)
           bouquet_add_service(bq, s, lcn, tag);
index 692b4b3f23ddadd856d545ad20850dd24764e398..06f8d1630b7a32782b75f771bf833d4d231b2bdc 100644 (file)
@@ -44,6 +44,9 @@ typedef struct bouquet {
   channel_tag_t*bq_chtag_ptr;
   const char   *bq_chtag_waiting;
   char         *bq_name;
+  char         *bq_ext_url;
+  int           bq_ssl_peer_verify;
+  int           bq_ext_url_period;
   char         *bq_src;
   char         *bq_comment;
   idnode_set_t *bq_services;
@@ -53,6 +56,8 @@ typedef struct bouquet {
   uint32_t      bq_lcn_offset;
   uint64_t      bq_last_lcn;
 
+  void         *bq_download;
+
 } bouquet_t;
 
 typedef RB_HEAD(,bouquet) bouquet_tree_t;
@@ -85,7 +90,7 @@ bouquet_t * bouquet_find_by_source(const char *name, const char *src, int create
 
 void bouquet_map_to_channels(bouquet_t *bq);
 void bouquet_notify_channels(bouquet_t *bq);
-void bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, uint32_t tag);
+void bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag);
 void bouquet_completed(bouquet_t *bq, uint32_t seen);
 void bouquet_change_comment(bouquet_t *bq, const char *comment, int replace);
 
index c3b0a4c6f26b139b5b2a40ce47a1f637df7c48e0..70e2df7160f60fc1d20fc8f928014d8c823b4af0 100644 (file)
@@ -475,4 +475,3 @@ htsbuf_to_string(htsbuf_queue_t *hq)
   htsbuf_read(hq, r, hq->hq_size);
   return r;
 }
-
index 283ecb7207f0d38646bf9f873af65266a93194b9..eb0a0574373fff4b6d918ab5689b2ecd219ca121 100644 (file)
@@ -1008,6 +1008,10 @@ mpegts_service_t *mpegts_service_create_raw(mpegts_mux_t *mm);
 mpegts_service_t *mpegts_service_find 
   ( mpegts_mux_t *mm, uint16_t sid, uint16_t pmt_pid, int create, int *save );
 
+service_t *
+mpegts_service_find_e2
+  ( uint32_t stype, uint32_t sid, uint32_t tsid, uint32_t onid, uint32_t hash);
+
 mpegts_service_t *
 mpegts_service_find_by_pid ( mpegts_mux_t *mm, int pid );
 
index 91382917f573aefb04c54f2b53f60bd38dcf526f..194b5ecf7fd7ab0314bd6fa5d69ca227f8f7f7d5 100644 (file)
@@ -589,7 +589,7 @@ dvb_freesat_add_service
     snprintf(name, sizeof(name), "%s: %s", bi->name, fr->name);
     fr->bouquet = bouquet_find_by_source(name, src, 1);
   }
-  bouquet_add_service(fr->bouquet, (service_t *)s, (int64_t)lcn * CHANNEL_SPLIT, 0);
+  bouquet_add_service(fr->bouquet, (service_t *)s, (int64_t)lcn * CHANNEL_SPLIT, NULL);
   return fr->bouquet->bq_enabled;
 }
 
@@ -1088,7 +1088,7 @@ dvb_bat_completed
       services_count++;
       if (bq->bq_enabled)
         bouquet_add_service(bq, (service_t *)bs->svc,
-                            (int64_t)bs->lcn * CHANNEL_SPLIT, 0);
+                            (int64_t)bs->lcn * CHANNEL_SPLIT, NULL);
     }
 
     bouquet_completed(bq, services_count);
index 5c9521a021176b67d12672d47f1e1ab9b86a72cf..7b1ca2caeaa1c93c5abb480fffeebecb77c71937 100644 (file)
@@ -107,7 +107,7 @@ iptv_bouquet_update(void *aux)
   bouquet_change_comment(bq, in->in_url, 1);
   LIST_FOREACH(mm, &in->mn_muxes, mm_network_link)
     LIST_FOREACH(ms, &mm->mm_services, s_dvb_mux_link) {
-      bouquet_add_service(bq, (service_t *)ms, ((iptv_mux_t *)mm)->mm_iptv_chnum, 0);
+      bouquet_add_service(bq, (service_t *)ms, ((iptv_mux_t *)mm)->mm_iptv_chnum, NULL);
       seen++;
     }
   bouquet_completed(bq, seen);
index d118037ad8fe1e105efcaf1561f94689b3e91a05..2a7ec866c173f3882a6beca19b4db9ab26d96912 100644 (file)
@@ -538,6 +538,64 @@ mpegts_service_channel_icon ( service_t *s )
   return NULL;
 }
 
+#if ENABLE_MPEGTS_DVB
+static int
+mpegts_service_match_network(mpegts_network_t *mn, uint32_t hash)
+{
+  int pos = hash >> 16, pos2;
+
+  pos = hash >> 16;
+  if (pos > 3600 || pos < 0) return 0;
+  pos = pos <= 1800 ? pos : pos - 3600;
+  if ((pos2 = dvb_network_get_orbital_pos(mn)) == INT_MAX) return 0;
+  return deltaI32(pos, pos2) <= 20;
+}
+#endif
+
+#if ENABLE_MPEGTS_DVB
+static int
+mpegts_service_match_mux(dvb_mux_t *mm, uint32_t hash)
+{
+  extern const idclass_t dvb_mux_dvbs_class;
+  dvb_mux_t *mmd;
+  int freq, pol;
+
+  if ((hash & 0xffff) == 0) return 1;
+  if (!idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) return 0;
+  freq = (hash & 0x7fff) * 1000;
+  pol = (hash & 0x8000) ? DVB_POLARISATION_HORIZONTAL : DVB_POLARISATION_VERTICAL;
+  mmd = mm;
+  return mmd->lm_tuning.u.dmc_fe_qpsk.orbital_pos == pol &&
+         deltaU32(mmd->lm_tuning.dmc_fe_freq, freq) < 2000;
+}
+#endif
+
+service_t *
+mpegts_service_find_e2(uint32_t stype, uint32_t sid, uint32_t tsid,
+                       uint32_t onid, uint32_t hash)
+{
+#if ENABLE_MPEGTS_DVB
+  mpegts_network_t *mn;
+  mpegts_mux_t *mm;
+  mpegts_service_t *s;
+
+  lock_assert(&global_lock);
+
+  LIST_FOREACH(mn, &mpegts_network_all, mn_global_link) {
+    if (!idnode_is_instance(&mn->mn_id, &dvb_network_class)) continue;
+    if (!mpegts_service_match_network(mn, hash)) continue;
+    LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) {
+      if (mm->mm_tsid != tsid || mm->mm_onid != onid) continue;
+      if (!mpegts_service_match_mux((dvb_mux_t *)mm, hash)) continue;
+      LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link)
+        if (s->s_dvb_service_id == sid)
+          return (service_t *)s;
+    }
+  }
+#endif
+  return NULL;
+}
+
 static void
 mpegts_service_mapped ( service_t *t )
 {
index af28c19a82f81593a7cebf00dc35fccc7f96f2b9..4f4e1446e796c7928bae4c68863d2fe7b590818d 100644 (file)
@@ -35,6 +35,8 @@ tvheadend.bouquet = function(panel, index)
 {
     var list = 'enabled,rescan,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' +
                'chtag,source,services_count,services_seen,comment';
+    var alist = 'enabled,ext_url,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' +
+                'chtag,comment';
 
     tvheadend.idnode_grid(panel, {
         url: 'api/bouquet',
@@ -58,6 +60,11 @@ tvheadend.bouquet = function(panel, index)
             comment:        { width: 200 }
         },
         list: list,
+        add: {
+            url: 'api/bouquet',
+            params: { list: alist },
+            create: { }
+        },
         del: true,
         edit: { params: { list: list } },
         sort: {