]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
epggrab: EPG channel mapping - move to idnode interface, fixes #3188
authorJaroslav Kysela <perex@perex.cz>
Thu, 22 Oct 2015 15:26:05 +0000 (17:26 +0200)
committerJaroslav Kysela <perex@perex.cz>
Thu, 22 Oct 2015 15:26:05 +0000 (17:26 +0200)
18 files changed:
src/api/api_epggrab.c
src/channels.c
src/channels.h
src/config.c
src/epggrab.c
src/epggrab.h
src/epggrab/channel.c
src/epggrab/module.c
src/epggrab/module/opentv.c
src/epggrab/module/pyepg.c
src/epggrab/module/xmltv.c
src/epggrab/otamux.c
src/epggrab/private.h
src/prop.c
src/prop.h
src/service.c
src/webui/static/app/epggrab.js
src/webui/static/app/tvheadend.js

index 83c341dd31bf261d61b6c82462a7de321b451e5d..0ad1a25d01e266f16ebeac0d09eea8f67c781e06 100644 (file)
 #include "api.h"
 #include "epggrab.h"
 
-static int
-api_epggrab_channel_list
-  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+static void
+api_epggrab_channel_grid
+  ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
 {
-  htsmsg_t *m;
-  pthread_mutex_lock(&global_lock);
-  m = epggrab_channel_list(0);
-  pthread_mutex_unlock(&global_lock);
-  *resp = htsmsg_create_map();
-  htsmsg_add_msg(*resp, "entries", m);
-  return 0;
+  epggrab_channel_t *ec;
+
+  TAILQ_FOREACH(ec, &epggrab_channel_entries, all_link)
+    idnode_set_add(ins, (idnode_t*)ec, &conf->filter, perm->aa_lang_ui);
 }
 
 static int
@@ -73,7 +70,10 @@ api_epggrab_ota_trigger
 void api_epggrab_init ( void )
 {
   static api_hook_t ah[] = {
-    { "epggrab/channel/list", ACCESS_ANONYMOUS, api_epggrab_channel_list, NULL },
+    { "epggrab/channel/list", ACCESS_ANONYMOUS, api_idnode_load_by_class, (void*)&epggrab_channel_class },
+    { "epggrab/channel/class", ACCESS_ADMIN, api_idnode_class, (void*)&epggrab_channel_class },
+    { "epggrab/channel/grid", ACCESS_ADMIN, api_idnode_grid, api_epggrab_channel_grid },
+
     { "epggrab/module/list",  ACCESS_ADMIN, api_epggrab_module_list, NULL },
     { "epggrab/config/load",  ACCESS_ADMIN, api_idnode_load_simple, &epggrab_conf.idnode },
     { "epggrab/config/save",  ACCESS_ADMIN, api_idnode_save_simple, &epggrab_conf.idnode },
index 8906d189dd2f0f9f14fc59a7c7617d845287ebb9..93e242ab537b1ed64b9ed312738e9ff35e9162c9 100644 (file)
@@ -233,48 +233,16 @@ static const void *
 channel_class_epggrab_get ( void *o )
 {
   channel_t *ch = o;
-  htsmsg_t *l = htsmsg_create_list();
-  epggrab_channel_link_t *ecl;
-  LIST_FOREACH(ecl, &ch->ch_epggrab, ecl_chn_link) {
-    if (!epggrab_channel_is_ota(ecl->ecl_epggrab))
-      htsmsg_add_str(l, NULL, epggrab_channel_get_id(ecl->ecl_epggrab));
-  }
-  return l;
+  return idnode_list_get2(&ch->ch_epggrab);
 }
 
 static int
 channel_class_epggrab_set ( void *o, const void *v )
 {
-  int save = 0;
   channel_t *ch = o;
-  htsmsg_t *l = (htsmsg_t*)v;
-  htsmsg_field_t *f;
-  epggrab_channel_t *ec;
-  epggrab_channel_link_t *ecl, *n;
-
-  /* mark for deletion */
-  LIST_FOREACH(ecl, &ch->ch_epggrab, ecl_chn_link) {
-    if (!epggrab_channel_is_ota(ecl->ecl_epggrab))
-      ecl->ecl_mark = 1;
-  }
-    
-  /* Link */
-  if (l) {
-    HTSMSG_FOREACH(f, l) {
-      if ((ec = epggrab_channel_find_by_id(htsmsg_field_get_str(f))))
-        save |= epggrab_channel_link(ec, ch);
-    }
-  }
-
-  /* Delete */
-  for (ecl = LIST_FIRST(&ch->ch_epggrab); ecl != NULL; ecl = n) {
-    n = LIST_NEXT(ecl, ecl_chn_link);
-    if (ecl->ecl_mark) {
-      epggrab_channel_link_delete(ecl, 1);
-      save = 1;
-    }
-  }
-  return save;
+  return idnode_list_set2(&ch->ch_id, &ch->ch_epggrab,
+                          &epggrab_channel_class, (htsmsg_t *)v,
+                          epggrab_channel_map);
 }
 
 static htsmsg_t *
@@ -283,7 +251,7 @@ channel_class_epggrab_list ( void *o, const char *lang )
   htsmsg_t *e, *m = htsmsg_create_map();
   htsmsg_add_str(m, "type",  "api");
   htsmsg_add_str(m, "uri",   "epggrab/channel/list");
-  htsmsg_add_str(m, "event", "epggrabchannel");
+  htsmsg_add_str(m, "event", "epggrab_channel");
   e = htsmsg_create_map();
   htsmsg_add_bool(e, "enum", 1);
   htsmsg_add_msg(m, "params", e);
index e542b714a90d064059fe6ce5967d52e9a788d97e..22479a447c683ac4559e3bf718a70b31ff426552 100644 (file)
@@ -50,7 +50,7 @@ typedef struct channel
   /* Channel info */
   int     ch_enabled;
   int     ch_autoname;
-  char   *ch_name; // Note: do not access directly!
+  char   *ch_name;                                 /* Note: do not access directly! */
   int64_t ch_number;
   char   *ch_icon;
   idnode_list_head_t ch_ctms;
@@ -69,7 +69,7 @@ typedef struct channel
   gtimer_t              ch_epg_timer_current;
 
   int ch_epgauto;
-  LIST_HEAD(,epggrab_channel_link) ch_epggrab;
+  idnode_list_head_t    ch_epggrab;                /* 1 = epggrab channel, 2 = channel */
 
   /* DVR */
   int                   ch_dvr_extra_time_pre;
index 5b7f558da15f34d0acdd8df7eac2a7b74fb9daf8..af7fe9b4d670fb88b674d57c3093498fd92d5105 100644 (file)
@@ -32,6 +32,7 @@
 #include "avahi.h"
 #include "url.h"
 #include "satip/server.h"
+#include "channels.h"
 
 #include <netinet/ip.h>
 
@@ -1346,6 +1347,39 @@ config_migrate_v22 ( void )
   }
 }
 
+/*
+ * v21 -> v23 : epggrab xmltv channels
+ */
+static void
+config_migrate_v23 ( void )
+{
+  htsmsg_t *c, *m, *n;
+  htsmsg_field_t *f;
+  uint32_t maj, min;
+  int64_t num;
+  tvh_uuid_t u;
+
+  if ((c = hts_settings_load_r(1, "epggrab/xmltv/channels")) != NULL) {
+    HTSMSG_FOREACH(f, c) {
+      m = htsmsg_field_get_map(f);
+      n = htsmsg_copy(m);
+      htsmsg_add_str(n, "id", f->hmf_name);
+      maj = htsmsg_get_u32_or_default(m, "major", 0);
+      min = htsmsg_get_u32_or_default(m, "minor", 0);
+      num = (maj * CHANNEL_SPLIT) + min;
+      if (num > 0)
+        htsmsg_add_s64(n, "lcn", num);
+      htsmsg_delete_field(n, "major");
+      htsmsg_delete_field(n, "minor");
+      uuid_init_hex(&u, NULL);
+      hts_settings_remove("epggrab/xmltv/channels/%s", f->hmf_name);
+      hts_settings_save(n, "epggrab/xmltv/channels/%s", u.hex);
+      htsmsg_destroy(n);
+    }
+    htsmsg_destroy(c);
+  }
+}
+
 
 
 /*
@@ -1466,7 +1500,8 @@ static const config_migrate_t config_migrate_table[] = {
   config_migrate_v19,
   config_migrate_v20,
   config_migrate_v21,
-  config_migrate_v22
+  config_migrate_v22,
+  config_migrate_v23
 };
 
 /*
index 799bf48e7df4c3e5171bf5a0ad2e086d1f280c5b..52f9687e1064c3fc4b40f1c1f6a9bd6fe05df047 100644 (file)
@@ -355,6 +355,8 @@ void epggrab_init ( void )
   pthread_mutex_init(&epggrab_mutex, NULL);
   pthread_cond_init(&epggrab_cond, NULL);
 
+  epggrab_channel_init();
+
   /* Initialise modules */
 #if ENABLE_MPEGTS
   eit_init();
@@ -395,12 +397,12 @@ void epggrab_done ( void )
     pthread_mutex_unlock(&global_lock);
     if (mod->done)
       mod->done(mod);
+    pthread_mutex_lock(&global_lock);
+    epggrab_channel_flush(mod->channels, 0);
     free((void *)mod->id);
     free((void *)mod->name);
     free(mod);
-    pthread_mutex_lock(&global_lock);
   }
-  pthread_mutex_unlock(&global_lock);
   epggrab_ota_shutdown();
   eit_done();
   opentv_done();
@@ -413,4 +415,5 @@ void epggrab_done ( void )
   free(epggrab_conf.ota_cron);
   epggrab_conf.ota_cron = NULL;
   epggrab_channel_done();
+  pthread_mutex_unlock(&global_lock);
 }
index 1ab559f92c41c68db2929d72e7fe100dfa1fe14a..ab071bc990ba88f1a2a3c7d59413845049010dde 100644 (file)
@@ -39,6 +39,7 @@ LIST_HEAD(epggrab_module_list, epggrab_module);
 typedef struct epggrab_module_list epggrab_module_list_t;
 
 struct mpegts_mux;
+struct channel;
 
 /* **************************************************************************
  * Grabber Stats
@@ -71,37 +72,34 @@ typedef struct epggrab_stats
 RB_HEAD(epggrab_channel_tree, epggrab_channel);
 typedef struct epggrab_channel_tree epggrab_channel_tree_t;
 
+TAILQ_HEAD(epggrab_channel_queue, epggrab_channel);
+
 /*
  * Grab channel
  */
 typedef struct epggrab_channel
 {
-  RB_ENTRY(epggrab_channel) link;     ///< Global link
+  idnode_t                  idnode;
+  TAILQ_ENTRY(epggrab_channel) all_link; ///< Global link
+  RB_ENTRY(epggrab_channel) link;     ///< Global tree link
+  epggrab_channel_tree_t    *tree;    ///< Member of this tree
   epggrab_module_t          *mod;     ///< Linked module
 
+  int                       enabled;  ///< Enabled/disabled
   char                      *id;      ///< Grabber's ID
 
   char                      *name;    ///< Channel name
   char                      *icon;    ///< Channel icon
-  int                       major;    ///< Channel major number
-  int                       minor;    ///< Channel minor number
+  char                      *comment; ///< Channel comment (EPG)
+  int64_t                   lcn;      ///< Channel number (split)
 
-  LIST_HEAD(,epggrab_channel_link) channels; ///< Mapped channels
+  idnode_list_head_t        channels; ///< Mapped channels (1 = epggrab channel, 2 = channel)
 } epggrab_channel_t;
 
-typedef struct epggrab_channel_link
-{
-  int                               ecl_mark;
-  struct channel                    *ecl_channel;
-  struct epggrab_channel            *ecl_epggrab;
-  LIST_ENTRY(epggrab_channel_link)  ecl_chn_link;
-  LIST_ENTRY(epggrab_channel_link)  ecl_epg_link;
-} epggrab_channel_link_t;
-
 /*
  * Access functions
  */
-htsmsg_t*         epggrab_channel_list      ( int ota );
+htsmsg_t *epggrab_channel_list      ( int ota );
 
 /*
  * Mutators
@@ -114,8 +112,9 @@ int epggrab_channel_set_number   ( epggrab_channel_t *ch, int major, int minor )
  * Updated/link
  */
 void epggrab_channel_updated     ( epggrab_channel_t *ch );
-void epggrab_channel_link_delete ( epggrab_channel_link_t *ecl, int delconf );
-int  epggrab_channel_link        ( epggrab_channel_t *ec, struct channel *ch );
+void epggrab_channel_link_delete ( epggrab_channel_t *ec, struct channel *ch, int delconf );
+int  epggrab_channel_link        ( epggrab_channel_t *ec, struct channel *ch, void *origin );
+int  epggrab_channel_map         ( idnode_t *ec, idnode_t *ch, void *origin );
 
 /* ID */
 const char *epggrab_channel_get_id ( epggrab_channel_t *ch );
@@ -155,12 +154,6 @@ struct epggrab_module
 
   /* Free */
   void      (*done)    ( void *m );
-
-  /* Channel listings */
-  void      (*ch_add)  ( void *m, struct channel *ch );
-  void      (*ch_rem)  ( void *m, struct channel *ch );
-  void      (*ch_mod)  ( void *m, struct channel *ch );
-  void      (*ch_save) ( void *m, epggrab_channel_t *ch );
 };
 
 /*
@@ -278,6 +271,8 @@ extern const idclass_t epggrab_class_mod;
 extern const idclass_t epggrab_class_mod_int;
 extern const idclass_t epggrab_class_mod_ext;
 extern const idclass_t epggrab_class_mod_ota;
+extern const idclass_t epggrab_channel_class;
+extern struct epggrab_channel_queue epggrab_channel_entries;
 
 /*
  * Access the Module list
index 9d828898c7092069ab51d321be652bb909051344..a01be7252314cedb756237a8207bbf473aea8da4 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  EPG Grabber - channel functions
  *  Copyright (C) 2012 Adam Sutton
+ *  Copyright (C) 2015 Jaroslav Kysela
  *
  *  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
@@ -27,6 +28,8 @@
 #include <assert.h>
 #include <string.h>
 
+struct epggrab_channel_queue epggrab_channel_entries;
+
 SKEL_DECLARE(epggrab_channel_skel, epggrab_channel_t);
 
 /* **************************************************************************
@@ -36,89 +39,102 @@ SKEL_DECLARE(epggrab_channel_skel, epggrab_channel_t);
 /* Check if channels match */
 int epggrab_channel_match ( epggrab_channel_t *ec, channel_t *ch )
 {
-  if (!ec || !ch || !ch->ch_epgauto || !ch->ch_enabled) return 0;
+  if (!ec || !ch || !ch->ch_epgauto || !ch->ch_enabled || !ec->enabled) return 0;
   if (LIST_FIRST(&ec->channels)) return 0; // ignore already paired
 
   if (ec->name && !strcmp(ec->name, channel_get_epgid(ch))) return 1;
-  int64_t number = channel_get_number(ch);
-  if ((ec->major || ec->minor) && ec->major == channel_get_major(number) && ec->minor == channel_get_minor(number)) return 1;
+  if (ec->lcn && ec->lcn == channel_get_number(ch)) return 1;
   return 0;
 }
 
 /* Destroy */
 void
 epggrab_channel_link_delete
-  ( epggrab_channel_link_t *ecl, int delconf )
+  ( epggrab_channel_t *ec, channel_t *ch, int delconf )
+{
+  idnode_list_mapping_t *ilm;
+  LIST_FOREACH(ilm, &ec->channels, ilm_in2_link)
+    if (ilm->ilm_in1 == &ec->idnode && ilm->ilm_in2 == &ch->ch_id)
+      idnode_list_unlink(ilm, NULL);
+}
+
+/* Destroy all links */
+static void epggrab_channel_links_delete( epggrab_channel_t *ec, int delconf )
 {
-  LIST_REMOVE(ecl, ecl_chn_link);
-  LIST_REMOVE(ecl, ecl_epg_link);
-  if (delconf && ecl->ecl_epggrab->mod->ch_save)
-    ecl->ecl_epggrab->mod->ch_save(ecl->ecl_epggrab->mod, ecl->ecl_epggrab);
-  free(ecl);
+  idnode_list_mapping_t *ilm;
+  while ((ilm = LIST_FIRST(&ec->channels)))
+    idnode_list_unlink(ilm, delconf ? ec : NULL);
 }
 
 /* Link epggrab channel to real channel */
 int
-epggrab_channel_link ( epggrab_channel_t *ec, channel_t *ch )
+epggrab_channel_link ( epggrab_channel_t *ec, channel_t *ch, void *origin )
 {
   int save = 0;
-  epggrab_channel_link_t *ecl;
+  idnode_list_mapping_t *ilm;
 
   /* No change */
   if (!ch || !ch->ch_enabled) return 0;
 
   /* Already linked */
-  LIST_FOREACH(ecl, &ec->channels, ecl_epg_link) {
-    if (ecl->ecl_channel == ch) {
-      ecl->ecl_mark = 0;
+  LIST_FOREACH(ilm, &ec->channels, ilm_in2_link)
+    if (ilm->ilm_in2 == &ch->ch_id)
       return 0;
-    }
-  }
 
   /* New link */
   tvhdebug(ec->mod->id, "linking %s to %s",
          ec->id, channel_get_name(ch));
-  ecl = calloc(1, sizeof(epggrab_channel_link_t));
-  ecl->ecl_channel = ch;
-  ecl->ecl_epggrab = ec;
-  LIST_INSERT_HEAD(&ec->channels, ecl, ecl_epg_link);
-  LIST_INSERT_HEAD(&ch->ch_epggrab, ecl, ecl_chn_link);
+
+  ilm = idnode_list_link(&ec->idnode, &ec->channels,
+                         &ch->ch_id, &ch->ch_epggrab,
+                         origin, 1);
+  if (ilm == NULL)
+    return 0;
+
   if (ec->name && epggrab_conf.channel_rename)
     save |= channel_set_name(ch, ec->name);
-  if ((ec->major > 0 || ec->minor > 0) && epggrab_conf.channel_renumber)
-    save |= channel_set_number(ch, ec->major, ec->minor);
+  if (ec->lcn > 0 && epggrab_conf.channel_renumber)
+    save |= channel_set_number(ch, ec->lcn / CHANNEL_SPLIT, ec->lcn % CHANNEL_SPLIT);
   if (ec->icon && epggrab_conf.channel_reicon)
     save |= channel_set_icon(ch, ec->icon);
   if (save)
     channel_save(ch);
 
-  /* Save */
-  if (ec->mod->ch_save) ec->mod->ch_save(ec->mod, ec);
+  if (origin == NULL)
+    epggrab_channel_save(ec);
   return 1;
 }
 
+int
+epggrab_channel_map ( idnode_t *ec, idnode_t *ch, void *origin )
+{
+  return epggrab_channel_link((epggrab_channel_t *)ec, (channel_t *)ch, origin);
+}
+
 /* Match and link (basically combines two funcs above for ease) */
 int epggrab_channel_match_and_link ( epggrab_channel_t *ec, channel_t *ch )
 {
   int r = epggrab_channel_match(ec, ch);
   if (r)
-    epggrab_channel_link(ec, ch);
+    epggrab_channel_link(ec, ch, NULL);
   return r;
 }
 
 /* Set name */
 int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name )
 {
+  idnode_list_mapping_t *ilm;
+  channel_t *ch;
   int save = 0;
   if (!ec || !name) return 0;
   if (!ec->name || strcmp(ec->name, name)) {
     if (ec->name) free(ec->name);
     ec->name = strdup(name);
     if (epggrab_conf.channel_rename) {
-      epggrab_channel_link_t *ecl;
-      LIST_FOREACH(ecl, &ec->channels, ecl_epg_link) {
-        if (channel_set_name(ecl->ecl_channel, name))
-          channel_save(ecl->ecl_channel);
+      LIST_FOREACH(ilm, &ec->channels, ilm_in2_link) {
+        ch = (channel_t *)ilm->ilm_in2;
+        if (channel_set_name(ch, name))
+          channel_save(ch);
       }
     }
     save = 1;
@@ -129,16 +145,18 @@ int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name )
 /* Set icon */
 int epggrab_channel_set_icon ( epggrab_channel_t *ec, const char *icon )
 {
+  idnode_list_mapping_t *ilm;
+  channel_t *ch;
   int save = 0;
   if (!ec || !icon) return 0;
   if (!ec->icon || strcmp(ec->icon, icon) ) {
     if (ec->icon) free(ec->icon);
     ec->icon = strdup(icon);
     if (epggrab_conf.channel_reicon) {
-      epggrab_channel_link_t *ecl;
-      LIST_FOREACH(ecl, &ec->channels, ecl_epg_link) {
-        if (channel_set_icon(ecl->ecl_channel, icon))
-          channel_save(ecl->ecl_channel);
+      LIST_FOREACH(ilm, &ec->channels, ilm_in2_link) {
+        ch = (channel_t *)ilm->ilm_in2;
+        if (channel_set_icon(ch, icon))
+          channel_save(ch);
       }
     }
     save = 1;
@@ -149,16 +167,21 @@ int epggrab_channel_set_icon ( epggrab_channel_t *ec, const char *icon )
 /* Set channel number */
 int epggrab_channel_set_number ( epggrab_channel_t *ec, int major, int minor )
 {
+  idnode_list_mapping_t *ilm;
+  channel_t *ch;
+  int64_t lcn;
   int save = 0;
   if (!ec || (major <= 0 && minor <= 0)) return 0;
-  if (ec->major != major || ec->minor != minor) {
-    ec->major = major;
-    ec->minor = minor;
+  lcn = (major * CHANNEL_SPLIT) + minor;
+  if (ec->lcn != lcn) {
+    ec->lcn = lcn;
     if (epggrab_conf.channel_renumber) {
-      epggrab_channel_link_t *ecl;
-      LIST_FOREACH(ecl, &ec->channels, ecl_epg_link) {
-        if (channel_set_number(ecl->ecl_channel, major, minor))
-          channel_save(ecl->ecl_channel);
+      LIST_FOREACH(ilm, &ec->channels, ilm_in2_link) {
+        ch = (channel_t *)ilm->ilm_in2;
+        if (channel_set_number(ch,
+                               lcn / CHANNEL_SPLIT,
+                               lcn % CHANNEL_SPLIT))
+          channel_save(ch);
       }
     }
     save = 1;
@@ -178,7 +201,7 @@ void epggrab_channel_updated ( epggrab_channel_t *ec )
       if (epggrab_channel_match_and_link(ec, ch)) break;
 
   /* Save */
-  if (ec->mod->ch_save) ec->mod->ch_save(ec->mod, ec);
+  epggrab_channel_save(ec);
 }
 
 /* ID comparison */
@@ -188,6 +211,36 @@ static int _ch_id_cmp ( void *a, void *b )
                 ((epggrab_channel_t*)b)->id);
 }
 
+/* Create new entry */
+epggrab_channel_t *epggrab_channel_create
+  ( epggrab_module_t *owner, htsmsg_t *conf, const char *uuid )
+{
+  epggrab_channel_t *ec;
+
+  if (htsmsg_get_str(conf, "id") == NULL)
+    return NULL;
+
+  ec = calloc(1, sizeof(*ec));
+  if (idnode_insert(&ec->idnode, uuid, &epggrab_channel_class, 0)) {
+    if (uuid)
+      tvherror("epggrab", "invalid uuid '%s'", uuid);
+    free(ec);
+    return NULL;
+  }
+
+  ec->mod = owner;
+  ec->enabled = 1;
+  ec->tree = owner->channels;
+
+  if (conf)
+    idnode_load(&ec->idnode, conf);
+
+  TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
+  if (RB_INSERT_SORTED(owner->channels, ec, link, _ch_id_cmp)) abort();
+
+  return ec;
+}
+
 /* Find/Create channel in the list */
 epggrab_channel_t *epggrab_channel_find
   ( epggrab_channel_tree_t *tree, const char *id, int create, int *save,
@@ -215,28 +268,47 @@ epggrab_channel_t *epggrab_channel_find
     ec = RB_INSERT_SORTED(tree, epggrab_channel_skel, link, _ch_id_cmp);
     if (!ec) {
       assert(owner);
-      ec      = epggrab_channel_skel;
+      ec       = epggrab_channel_skel;
       SKEL_USED(epggrab_channel_skel);
-      ec->id  = strdup(ec->id);
-      ec->mod = owner;
-      *save   = 1;
+      ec->enabled = 1;
+      ec->tree = tree;
+      ec->id   = strdup(ec->id);
+      ec->mod  = owner;
+      TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
+
+      if (idnode_insert(&ec->idnode, NULL, &epggrab_channel_class, 0))
+        abort();
+      *save    = 1;
       return ec;
     }
   }
   return ec;
 }
 
-void epggrab_channel_destroy
-  ( epggrab_channel_tree_t *tree, epggrab_channel_t *ec, int delconf )
+void epggrab_channel_save( epggrab_channel_t *ec )
 {
-  epggrab_channel_link_t *ecl;
+  htsmsg_t *m = htsmsg_create_map();
+  idnode_save(&ec->idnode, m);
+  hts_settings_save(m, "epggrab/%s/channels/%s",
+                    ec->mod->id, idnode_uuid_as_sstr(&ec->idnode));
+  htsmsg_destroy(m);
+}
 
-  if (!ec) return;
+void epggrab_channel_destroy( epggrab_channel_t *ec, int delconf )
+{
+  if (ec == NULL) return;
 
   /* Already linked */
-  while ((ecl = LIST_FIRST(&ec->channels)) != NULL)
-    epggrab_channel_link_delete(ecl, delconf);
-  RB_REMOVE(tree, ec, link);
+  epggrab_channel_links_delete(ec, 0);
+  RB_REMOVE(ec->tree, ec, link);
+  TAILQ_REMOVE(&epggrab_channel_entries, ec, all_link);
+  idnode_unlink(&ec->idnode);
+
+  if (delconf)
+    hts_settings_remove("epggrab/%s/channels/%s",
+                        ec->mod->id, idnode_uuid_as_sstr(&ec->idnode));
+
+  free(ec->comment);
   free(ec->name);
   free(ec->icon);
   free(ec->id);
@@ -247,60 +319,40 @@ void epggrab_channel_flush
   ( epggrab_channel_tree_t *tree, int delconf )
 {
   epggrab_channel_t *ec;
-  while ((ec = RB_FIRST(tree)) != NULL)
-    epggrab_channel_destroy(tree, ec, delconf);
+  if (tree == NULL)
+    return;
+  while ((ec = RB_FIRST(tree)) != NULL) {
+    assert(tree == ec->tree);
+    epggrab_channel_destroy(ec, delconf);
+  }
 }
 
 /* **************************************************************************
  * Global routines
  * *************************************************************************/
 
-htsmsg_t *epggrab_channel_list ( int ota )
+void epggrab_channel_add ( channel_t *ch )
 {
-  char name[500];
   epggrab_module_t *mod;
-  epggrab_channel_t *ec;
-  htsmsg_t *e, *m;
-  m = htsmsg_create_list();
-  LIST_FOREACH(mod, &epggrab_modules, link) {
-    if (!ota && (mod->type == EPGGRAB_OTA)) continue;
-    if (mod->channels) {
-      RB_FOREACH(ec, mod->channels, link) {
-        e = htsmsg_create_map();
-        snprintf(name, sizeof(name), "%s|%s", mod->id, ec->id);
-        htsmsg_add_str(e, "key", name);
-        snprintf(name, sizeof(name), "%s: %s (%s)",
-                 mod->name, ec->name ?: ec->id, ec->id);
-        htsmsg_add_str(e, "val", name);
-        htsmsg_add_msg(m, NULL, e);
-      }
-    }
-  }
-  return m;
-}
+  epggrab_channel_t *egc;
 
-void epggrab_channel_add ( channel_t *ch )
-{
-  epggrab_module_t *m;
-  LIST_FOREACH(m, &epggrab_modules, link) {
-    if (m->ch_add) m->ch_add(m, ch);
-  }
+  LIST_FOREACH(mod, &epggrab_modules, link)
+    RB_FOREACH(egc, mod->channels, link)
+      if (epggrab_channel_match_and_link(egc, ch))
+        break;
 }
 
 void epggrab_channel_rem ( channel_t *ch )
 {
-  epggrab_module_t *m;
-  LIST_FOREACH(m, &epggrab_modules, link) {
-    if (m->ch_rem) m->ch_rem(m, ch);
-  }
+  idnode_list_mapping_t *ilm;
+
+  while ((ilm = LIST_FIRST(&ch->ch_epggrab)) != NULL)
+    idnode_list_unlink(ilm, ch);
 }
 
 void epggrab_channel_mod ( channel_t *ch )
 {
-  epggrab_module_t *m;
-  LIST_FOREACH(m, &epggrab_modules, link) {
-    if (m->ch_mod) m->ch_mod(m, ch);
-  }
+  return epggrab_channel_add(ch);
 }
 
 const char *
@@ -332,8 +384,143 @@ epggrab_channel_is_ota ( epggrab_channel_t *ec )
   return ec->mod->type == EPGGRAB_OTA;
 }
 
+/*
+ * Class
+ */
+
+static const char *
+epggrab_channel_class_get_title(idnode_t *self, const char *lang)
+{
+  epggrab_channel_t *ec = (epggrab_channel_t*)self;
+
+  snprintf(prop_sbuf, PROP_SBUF_LEN, "%s: %s (%s)",
+           ec->mod->name, ec->name ?: ec->id, ec->id);
+  return prop_sbuf;
+}
+
+static void
+epggrab_channel_class_save(idnode_t *self)
+{
+  epggrab_channel_save((epggrab_channel_t *)self);
+}
+
+static void
+epggrab_channel_class_delete(idnode_t *self)
+{
+  epggrab_channel_destroy((epggrab_channel_t *)self, 1);
+}
+
+static const void *
+epggrab_channel_class_module_get ( void *obj )
+{
+  epggrab_channel_t *ec = obj;
+  snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ec->mod->name ?: "");
+  return &prop_sbuf_ptr;
+}
+
+static const void *
+epggrab_channel_class_channels_get ( void *obj )
+{
+  epggrab_channel_t *ec = obj;
+  return idnode_list_get1(&ec->channels);
+}
+
+static int
+epggrab_channel_class_channels_set ( void *obj, const void *p )
+{
+  epggrab_channel_t *ec = obj;
+  return idnode_list_set1(&ec->idnode, &ec->channels,
+                          &channel_class, (htsmsg_t *)p,
+                          epggrab_channel_map);
+}
+
+static char *
+epggrab_channel_class_channels_rend ( void *obj, const char *lang )
+{
+  epggrab_channel_t *ec = obj;
+  return idnode_list_get_csv1(&ec->channels, lang);
+}
+
+
+const idclass_t epggrab_channel_class = {
+  .ic_class      = "epggrab_channel",
+  .ic_caption    = N_("EPG grabber channel"),
+  .ic_event      = "epggrab_channel",
+  .ic_perm_def   = ACCESS_ADMIN,
+  .ic_save       = epggrab_channel_class_save,
+  .ic_get_title  = epggrab_channel_class_get_title,
+  .ic_delete     = epggrab_channel_class_delete,
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_BOOL,
+      .id       = "enabled",
+      .name     = N_("Enabled"),
+      .off      = offsetof(epggrab_channel_t, enabled),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "module",
+      .name     = N_("Module"),
+      .get      = epggrab_channel_class_module_get,
+      .opts     = PO_RDONLY | PO_NOSAVE,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "id",
+      .name     = N_("ID"),
+      .off      = offsetof(epggrab_channel_t, id),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "name",
+      .name     = N_("Name"),
+      .off      = offsetof(epggrab_channel_t, name),
+    },
+    {
+      .type     = PT_S64,
+      .intsplit = CHANNEL_SPLIT,
+      .id       = "number",
+      .name     = N_("Number"),
+      .off      = offsetof(epggrab_channel_t, lcn),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "icon",
+      .name     = N_("Icon"),
+      .off      = offsetof(epggrab_channel_t, icon),
+    },
+    {
+      .type     = PT_STR,
+      .islist   = 1,
+      .id       = "channels",
+      .name     = N_("Channels"),
+      .set      = epggrab_channel_class_channels_set,
+      .get      = epggrab_channel_class_channels_get,
+      .list     = channel_class_get_list,
+      .rend     = epggrab_channel_class_channels_rend,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "comment",
+      .name     = N_("Comment"),
+      .off      = offsetof(epggrab_channel_t, comment)
+    },
+    {}
+  }
+};
+
+/*
+ *
+ */
+void
+epggrab_channel_init( void )
+{
+  TAILQ_INIT(&epggrab_channel_entries);
+}
+
 void
 epggrab_channel_done( void )
 {
+  assert(TAILQ_FIRST(&epggrab_channel_entries) == NULL);
   SKEL_FREE(epggrab_channel_skel);
 }
index 540c3259be7b1efa5c877a659fdb4862c03d099f..d662d2a4c07c7cf20c5c7a90fad246166f8904f2 100644 (file)
@@ -204,12 +204,6 @@ epggrab_module_t *epggrab_module_create
   skel->name     = strdup(name);
   skel->priority = priority;
   skel->channels = channels;
-  if (channels) {
-    skel->ch_save = epggrab_module_ch_save;
-    skel->ch_add  = epggrab_module_ch_add;
-    skel->ch_mod  = epggrab_module_ch_mod;
-    skel->ch_rem  = epggrab_module_ch_rem;
-  }
 
   /* Insert */
   assert(!epggrab_module_find_by_id(id));
@@ -264,96 +258,15 @@ void epggrab_module_parse( void *m, htsmsg_t *data )
  * Module channel routines
  * *************************************************************************/
 
-void epggrab_module_ch_save ( void *_m, epggrab_channel_t *ch )
-{
-  htsmsg_t *a = NULL, *m = htsmsg_create_map();
-  epggrab_module_t *mod = _m;
-  epggrab_channel_link_t *ecl;
-
-  if (ch->name)
-    htsmsg_add_str(m, "name", ch->name);
-  if (ch->icon)
-    htsmsg_add_str(m, "icon", ch->icon);
-  LIST_FOREACH(ecl, &ch->channels, ecl_epg_link) {
-    if (!a) a = htsmsg_create_list();
-    htsmsg_add_str(a, NULL, channel_get_suuid(ecl->ecl_channel));
-  }
-  if (a) htsmsg_add_msg(m, "channels", a);
-  if (ch->major)
-    htsmsg_add_u32(m, "major", ch->major);
-  if (ch->minor)
-    htsmsg_add_u32(m, "minor", ch->minor);
-
-  hts_settings_save(m, "epggrab/%s/channels/%s", mod->id, ch->id);
-  htsmsg_destroy(m);
-}
-
-void epggrab_module_ch_add ( void *m, channel_t *ch )
-{
-  epggrab_channel_t *egc;
-  epggrab_module_int_t *mod = m;
-  RB_FOREACH(egc, mod->channels, link) {
-    if (epggrab_channel_match_and_link(egc, ch)) break;
-  }
-}
-
-void epggrab_module_ch_rem ( void *m, channel_t *ch )
-{
-  epggrab_channel_link_t *ecl;
-  while ((ecl = LIST_FIRST(&ch->ch_epggrab)))
-    epggrab_channel_link_delete(ecl, 1);
-}
-
-void epggrab_module_ch_mod ( void *mod, channel_t *ch )
-{
-  return epggrab_module_ch_add(mod, ch);
-}
-
-static void _epggrab_module_channel_load 
-  ( epggrab_module_t *mod, htsmsg_t *m, const char *id )
-{
-  int save = 0;
-  const char *str;
-  uint32_t u32;
-  htsmsg_t *a;
-  htsmsg_field_t *f;
-  channel_t *ch;
-  epggrab_channel_t *egc;
-  
-  egc  = epggrab_channel_find(mod->channels, id, 1, &save, mod);
-
-  if ((str = htsmsg_get_str(m, "name")))
-    egc->name = strdup(str);
-  if ((str = htsmsg_get_str(m, "icon")))
-    egc->icon = strdup(str);
-  if(!htsmsg_get_u32(m, "major", &u32))
-    egc->major = u32;
-  if(!htsmsg_get_u32(m, "minor", &u32))
-    egc->minor = u32;
-  if ((a = htsmsg_get_list(m, "channels"))) {
-    HTSMSG_FOREACH(f, a) {
-      if ((str = htsmsg_field_get_str(f))) {
-        if ((ch = channel_find_by_uuid(str)))
-          epggrab_channel_link(egc, ch);
-      }
-    }
-
-  /* Compat with older 3.1 code */
-  } else if (!htsmsg_get_u32(m, "channel", &u32)) {
-    if ((ch = channel_find_by_id(u32)))
-      epggrab_channel_link(egc, ch);
-  }
-}
-
 void epggrab_module_channels_load ( epggrab_module_t *mod )
 {
   htsmsg_t *m, *e;
   htsmsg_field_t *f;
   if (!mod || !mod->channels) return;
-  if ((m = hts_settings_load("epggrab/%s/channels", mod->id))) {
+  if ((m = hts_settings_load_r(1, "epggrab/%s/channels", mod->id))) {
     HTSMSG_FOREACH(f, m) {
       if ((e = htsmsg_get_map_by_field(f)))
-        _epggrab_module_channel_load(mod, e, f->hmf_name);
+        epggrab_channel_create(mod, e, f->hmf_name);
     }
     htsmsg_destroy(m);
   }
index 3b61e7ba59a7d5a7fcd3be4c69710bb1e87f31b7..ad53f5f9f634a23fae78d54b3a7e0f812ccfe8fe 100644 (file)
@@ -443,7 +443,7 @@ opentv_parse_event_section
 {
   opentv_module_t *mod = sta->os_mod;
   epggrab_channel_t *ec;
-  epggrab_channel_link_t *ecl;
+  idnode_list_mapping_t *ilm;
   const char *lang = NULL;
   int save = 0;
 
@@ -456,8 +456,10 @@ opentv_parse_event_section
   if (!(ec = _opentv_find_epggrab_channel(mod, cid, 0, NULL))) return 0;
 
   /* Iterate all channels */
-  LIST_FOREACH(ecl, &ec->channels, ecl_epg_link)
-    save |= opentv_parse_event_section_one(sta, cid, mjd, ecl->ecl_channel, lang, buf, len);
+  LIST_FOREACH(ilm, &ec->channels, ilm_in2_link)
+    save |= opentv_parse_event_section_one(sta, cid, mjd,
+                                           (channel_t *)ilm->ilm_in2,
+                                           lang, buf, len);
 
   /* Update EPG */
   if (save) epg_updated();
@@ -476,7 +478,7 @@ opentv_desc_channels
   opentv_status_t *sta = mt->mt_opaque;
   opentv_module_t *mod = sta->os_mod;
   epggrab_channel_t *ec;
-  epggrab_channel_link_t *ecl;
+  idnode_list_mapping_t *ilm;
   mpegts_service_t *svc;
   channel_t *ch;
   int sid, cid, cnum, unk;
@@ -516,17 +518,17 @@ opentv_desc_channels
 skip_chnum:
     if (svc && LIST_FIRST(&svc->s_channels)) {
       ec  =_opentv_find_epggrab_channel(mod, cid, 1, &save);
-      ecl = LIST_FIRST(&ec->channels);
+      ilm = LIST_FIRST(&ec->channels);
       ch  = (channel_t *)LIST_FIRST(&svc->s_channels)->ilm_in2;
-      tvhtrace(mt->mt_name, "       ec = %p, ecl = %p", ec, ecl);
+      tvhtrace(mt->mt_name, "       ec = %p, ilm = %p", ec, ilm);
 
-      if (ecl && ecl->ecl_channel != ch) {
-        epggrab_channel_link_delete(ecl, 1);
-        ecl = NULL;
+      if (ilm && ilm->ilm_in2 != &ch->ch_id) {
+        epggrab_channel_link_delete(ec, ch, 1);
+        ilm = NULL;
       }
       
-      if (!ecl)
-        epggrab_channel_link(ec, ch);
+      if (!ilm)
+        epggrab_channel_link(ec, ch, NULL);
       save |= epggrab_channel_set_number(ec, cnum, 0);
     }
     i += 9;
@@ -959,7 +961,6 @@ static int _opentv_prov_load_one ( const char *id, htsmsg_t *m )
   mod->title    = _pid_list_to_array(tl);
   mod->summary  = _pid_list_to_array(sl);
   mod->channels = &_opentv_channels;
-  mod->ch_rem   = epggrab_module_ch_rem;
   _opentv_compile_pattern_list(&mod->p_snum, htsmsg_get_list(m, "season_num"));
   _opentv_compile_pattern_list(&mod->p_enum, htsmsg_get_list(m, "episode_num"));
   _opentv_compile_pattern_list(&mod->p_pnum, htsmsg_get_list(m, "part_num"));
index 3bc63dc51b2b5008d135e4ba67a0350156fabef0..4834fc75a20ae0c6830e39b32b925675bb4ebe53 100644 (file)
@@ -354,7 +354,7 @@ static int _pyepg_parse_schedule
   htsmsg_field_t *f;
   epggrab_channel_t *ec;
   const char *str;
-  epggrab_channel_link_t *ecl;
+  idnode_list_mapping_t *ilm;
 
   if ( data == NULL ) return 0;
 
@@ -365,9 +365,9 @@ static int _pyepg_parse_schedule
 
   HTSMSG_FOREACH(f, tags) {
     if (strcmp(f->hmf_name, "broadcast") == 0) {
-      LIST_FOREACH(ecl, &ec->channels, ecl_epg_link)
+      LIST_FOREACH(ilm, &ec->channels, ilm_in2_link)
         save |= _pyepg_parse_broadcast(mod, htsmsg_get_map_by_field(f),
-                                       ecl->ecl_channel, stats);
+                                       (channel_t *)ilm->ilm_in2, stats);
     }
   }
 
index 35c00018f19962aaf27ae000d48b68ab033835fb..2c300c402d73eaf73e6710df96fd95d30775c9c0 100644 (file)
@@ -580,21 +580,21 @@ static int _xmltv_parse_programme
   htsmsg_t *attribs, *tags, *subtag;
   const char *s, *chid, *icon = NULL;
   time_t start, stop;
-  epggrab_channel_t *ch;
-  epggrab_channel_link_t *ecl;
+  epggrab_channel_t *ec;
+  idnode_list_mapping_t *ilm;
 
   if(body == NULL) return 0;
 
   if((attribs = htsmsg_get_map(body,    "attrib"))  == NULL) return 0;
   if((tags    = htsmsg_get_map(body,    "tags"))    == NULL) return 0;
   if((chid    = htsmsg_get_str(attribs, "channel")) == NULL) return 0;
-  if((ch      = _xmltv_channel_find(chid, 1, &chsave)) == NULL) return 0;
+  if((ec      = _xmltv_channel_find(chid, 1, &chsave)) == NULL) return 0;
   if (chsave) {
-    epggrab_channel_updated(ch);
+    epggrab_channel_updated(ec);
     stats->channels.created++;
     stats->channels.modified++;
   }
-  if (!LIST_FIRST(&ch->channels)) return 0;
+  if (!LIST_FIRST(&ec->channels)) return 0;
   if((s       = htsmsg_get_str(attribs, "start"))   == NULL) return 0;
   start = _xmltv_str2time(s);
   if((s       = htsmsg_get_str(attribs, "stop"))    == NULL) return 0;
@@ -606,8 +606,8 @@ static int _xmltv_parse_programme
 
   if(stop <= start || stop <= dispatch_clock) return 0;
 
-  LIST_FOREACH(ecl, &ch->channels, ecl_epg_link)
-    save |= _xmltv_parse_programme_tags(mod, ecl->ecl_channel, tags,
+  LIST_FOREACH(ilm, &ec->channels, ilm_in2_link)
+    save |= _xmltv_parse_programme_tags(mod, (channel_t *)ilm->ilm_in2, tags,
                                         start, stop, icon, stats);
   return save;
 }
index 30920c7a18a7317e6b34eb5e235dd37825380bc5..b541c0e69d1413040b51063be1e4cf5e67954c32 100644 (file)
@@ -880,7 +880,6 @@ epggrab_ota_shutdown ( void )
 {
   epggrab_ota_mux_t *ota;
 
-  pthread_mutex_lock(&global_lock);
   epggrab_ota_running = 0;
   while ((ota = TAILQ_FIRST(&epggrab_ota_active)) != NULL)
     epggrab_ota_free(&epggrab_ota_active, ota);
@@ -888,7 +887,6 @@ epggrab_ota_shutdown ( void )
     epggrab_ota_free(&epggrab_ota_pending, ota);
   while ((ota = RB_FIRST(&epggrab_ota_all)) != NULL)
     epggrab_ota_free(NULL, ota);
-  pthread_mutex_unlock(&global_lock);
   SKEL_FREE(epggrab_ota_mux_skel);
   SKEL_FREE(epggrab_svc_link_skel);
   free(epggrab_ota_cron_multi);
index 1e97c25a26d71eef85384d620a6a6e55df634655..660b74b3941709b6ef08d196f8531d7e885d1fcf 100644 (file)
@@ -50,15 +50,20 @@ int  epggrab_channel_match ( epggrab_channel_t *ec, struct channel *ch );
 int  epggrab_channel_match_and_link
   ( epggrab_channel_t *ec, struct channel *ch );
 
+epggrab_channel_t *epggrab_channel_create
+  ( epggrab_module_t *owner, htsmsg_t *conf, const char *uuid );
+
 epggrab_channel_t *epggrab_channel_find
   ( epggrab_channel_tree_t *chs, const char *id, int create, int *save,
     epggrab_module_t *owner );
 
+void epggrab_channel_save ( epggrab_channel_t *ec );
 void epggrab_channel_destroy
-  ( epggrab_channel_tree_t *tree, epggrab_channel_t *ec, int delconf );
+  ( epggrab_channel_t *ec, int delconf );
 void epggrab_channel_flush
   ( epggrab_channel_tree_t *tree, int delconf );
 
+void epggrab_channel_init(void);
 void epggrab_channel_done(void);
 
 /* **************************************************************************
index 3eb24627b7a600d814601924b603864dcaf7c064..aceb7500ef5695748e7d88d5889648c8dac4f5ac 100644 (file)
@@ -26,6 +26,7 @@
 #include "lang_str.h"
 
 char prop_sbuf[PROP_SBUF_LEN];
+char *prop_sbuf_ptr = prop_sbuf;
 
 /* **************************************************************************
  * Utilities
index f2f35d1523db67b9d9c5773fe5f8cd6073805e2e..bd44b94ba750baca9f4c548a97847820aefea75b 100644 (file)
@@ -104,6 +104,7 @@ typedef struct property {
 
 #define PROP_SBUF_LEN 4096
 extern char prop_sbuf[PROP_SBUF_LEN];
+extern char *prop_sbuf_ptr;
 
 const property_t *prop_find(const property_t *p, const char *name);
 
index 1631005abf12ba038ce0f527d175a6f930e50e6c..f72498d381e7981c955756a9d5ceb95a3ba6abe9 100644 (file)
@@ -1722,25 +1722,6 @@ service_mapped(service_t *s)
   if (s->s_mapped) s->s_mapped(s);
 }
 
-/*
- * Find the primary EPG service (to stop EPG trying to update
- * from multiple OTA sources)
- */
-#ifdef MOVE_TO_MPEGTS
-int
-service_is_primary_epg(service_t *svc)
-{
-  service_t *ret = NULL, *t;
-  if (!svc || !svc->s_ch) return 0;
-  LIST_FOREACH(t, &svc->s_ch->ch_services, s_ch_link) {
-    if (!t->s_is_enabled(t) || !t->s_dvb_eit_enable) continue;
-    if (!ret)
-      ret = t;
-  }
-  return !ret ? 0 : (ret->s_dvb_service_id == svc->s_dvb_service_id);
-}
-#endif
-
 /*
  * list of known service types
  */
index 5686c8b9ad3b4c3cdfda5e13227c08f6acc97ca9..7f94567d12d9a7978698527b0f8b47fc61bf8b9a 100644 (file)
@@ -33,6 +33,29 @@ tvheadend.epggrab_base = function(panel, index) {
 
 }
 
+tvheadend.epggrab_map = function(panel, index) {
+
+    tvheadend.idnode_grid(panel, {
+        url: 'api/epggrab/channel',
+        all: 1,
+        titleS: _('EPG Grabber Channel'),
+        titleP: _('EPG Grabber Channels'),
+        iconCls: 'baseconf',
+        tabIndex: index,
+        del: true,
+        sort: {
+          field: 'name',
+          direction: 'ASC'
+        },
+        help: function() {
+            new tvheadend.help(_('EPG Grabber Channels'), 'config_epggrab.html');
+        }
+    });
+
+    return panel;
+
+}
+
 tvheadend.epggrab_mod = function(panel, index) {
 
     var actions = new Ext.ux.grid.RowActions({
index 10cfd5bb1ad2286da682cd695e1a0382b09adde8..9060aac2229de5ef3ff54fc60a2d405e8e7fbd24 100644 (file)
@@ -482,6 +482,7 @@ function accessUpdate(o) {
         tvheadend.channel_tab(chepg);
         tvheadend.cteditor(chepg);
         tvheadend.bouquet(chepg);
+        tvheadend.epggrab_map(chepg);
         tvheadend.epggrab_base(chepg);
         tvheadend.epggrab_mod(chepg);