]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
caclient: initial code to move cwc,capmt to idnode
authorJaroslav Kysela <perex@perex.cz>
Tue, 30 Sep 2014 10:35:51 +0000 (12:35 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 30 Sep 2014 19:46:51 +0000 (21:46 +0200)
17 files changed:
Makefile
src/api.c
src/api.h
src/api/api_caclient.c [new file with mode: 0644]
src/descrambler/caclient.c [new file with mode: 0644]
src/descrambler/caclient.h [new file with mode: 0644]
src/descrambler/capmt.c
src/descrambler/capmt.h [deleted file]
src/descrambler/cwc.c
src/descrambler/cwc.h [deleted file]
src/descrambler/descrambler.c
src/main.c
src/prop.c
src/webui/extjs.c
src/webui/static/app/caclient.js [new file with mode: 0644]
src/webui/static/app/idnode.js
src/webui/static/app/tvheadend.js

index e430ce46b0da5e6d69f865c477aa730cd16d35dc..7cb65df145f199c20e2b93165a129a4436c43143 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -157,7 +157,8 @@ SRCS += \
        src/api/api_esfilter.c \
        src/api/api_intlconv.c \
        src/api/api_access.c \
-       src/api/api_dvr.c
+       src/api/api_dvr.c \
+       src/api/api_caclient.c
 
 SRCS += \
        src/parsers/parsers.c \
@@ -203,6 +204,7 @@ SRCS += src/muxer.c \
 # MPEGTS core
 SRCS-$(CONFIG_MPEGTS) += \
        src/descrambler/descrambler.c \
+       src/descrambler/caclient.c \
        src/input/mpegts.c \
        src/input/mpegts/mpegts_input.c \
        src/input/mpegts/mpegts_network.c \
index 4eadf940932232d6b6f6fbd893709337fd162520..f0ac5be3b68858bbafa9f0402c51069054392b44 100644 (file)
--- a/src/api.c
+++ b/src/api.c
@@ -133,6 +133,7 @@ void api_init ( void )
   api_intlconv_init();
   api_access_init();
   api_dvr_init();
+  api_caclient_init();
 }
 
 void api_done ( void )
index 08e0869d43fa0c4fb1f2da9cd16565645ef1a937..c6fe371cc6bf68812453bb8373a1c8d071cb8405 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -62,7 +62,6 @@ void api_init               ( void );
 void api_done               ( void );
 void api_idnode_init        ( void );
 void api_input_init         ( void );
-void api_input_satip_init   ( void );
 void api_service_init       ( void );
 void api_channel_init       ( void );
 void api_mpegts_init        ( void );
@@ -74,6 +73,7 @@ void api_esfilter_init      ( void );
 void api_intlconv_init      ( void );
 void api_access_init        ( void );
 void api_dvr_init           ( void );
+void api_caclient_init      ( void );
 
 /*
  * IDnode
diff --git a/src/api/api_caclient.c b/src/api/api_caclient.c
new file mode 100644 (file)
index 0000000..e186ec2
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ *  tvheadend - API access to Conditional Access Clients
+ *
+ *  Copyright (C) 2014 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
+ *  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 "tvheadend.h"
+#include "access.h"
+#include "htsmsg.h"
+#include "api.h"
+#include "descrambler/caclient.h"
+
+/*
+ *
+ */
+static int
+api_caclient_list
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  caclient_t *cac;
+  htsmsg_t *l, *e;
+
+  l = htsmsg_create_list();
+  TAILQ_FOREACH(cac, &caclients, cac_link) {
+    e = htsmsg_create_map();
+    htsmsg_add_str(e, "key", idnode_uuid_as_str(&cac->cac_id));
+    htsmsg_add_str(e, "val", idnode_get_title(&cac->cac_id));
+    htsmsg_add_msg(l, NULL, e);
+  }
+  *resp = htsmsg_create_map();
+  htsmsg_add_msg(*resp, "entries", l);
+  return 0;
+}
+
+static int
+api_caclient_builders
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  const idclass_t **r;
+  htsmsg_t *l, *e;
+
+  /* List of available builder classes */
+  l = htsmsg_create_list();
+  for (r = caclient_classes; *r; r++)
+    if ((e = idclass_serialize(*r)))
+      htsmsg_add_msg(l, NULL, e);
+
+  /* Output */
+  *resp = htsmsg_create_map();
+  htsmsg_add_msg(*resp, "entries", l);
+
+  return 0;
+}
+
+static int
+api_caclient_create
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  int err = 0;
+  const char *clazz;
+  htsmsg_t *conf;
+
+  if (!(clazz = htsmsg_get_str(args, "class")))
+    return EINVAL;
+  if (!(conf  = htsmsg_get_map(args, "conf")))
+    return EINVAL;
+  htsmsg_set_str(conf, "class", clazz);
+
+  pthread_mutex_lock(&global_lock);
+  if (caclient_create(NULL, conf, 1) == NULL)
+    err = -EINVAL;
+  pthread_mutex_unlock(&global_lock);
+
+  return err;
+}
+
+/*
+ * Init
+ */
+void
+api_caclient_init ( void )
+{
+  static api_hook_t ah[] = {
+    { "caclient/list",       ACCESS_ADMIN, api_caclient_list,     NULL },
+    { "caclient/class",      ACCESS_ADMIN, api_idnode_class, (void*)&caclient_class },
+    { "caclient/builders",   ACCESS_ADMIN, api_caclient_builders, NULL },
+    { "caclient/create",     ACCESS_ADMIN, api_caclient_create,   NULL },
+    { NULL },
+  };
+
+  api_register_all(ah);
+}
diff --git a/src/descrambler/caclient.c b/src/descrambler/caclient.c
new file mode 100644 (file)
index 0000000..759e454
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ *  Tvheadend - conditional access key client superclass
+ *  Copyright (C) 2014 Jaroslav Kysela <perex@perex.cz>
+ *
+ *  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 "tvheadend.h"
+#include "settings.h"
+#include "caclient.h"
+
+const idclass_t *caclient_classes[] = {
+#if ENABLE_CWC
+  &caclient_cwc_class,
+#endif
+#if ENABLE_CAPMT
+  &caclient_capmt_class,
+#endif
+  NULL
+};
+
+struct caclient_entry_queue caclients;
+static pthread_mutex_t caclients_mutex;
+
+static void caclient_class_save ( idnode_t *in );
+
+static const idclass_t *
+caclient_class_find(const char *name)
+{
+  const idclass_t **r;
+  for (r = caclient_classes; *r; r++) {
+    if (strcmp((*r)->ic_class, name) == 0)
+      return *r;
+  }
+  return NULL;
+}
+
+static void
+caclient_reindex(void)
+{
+  caclient_t *cac;
+  int i = 1;
+
+  TAILQ_FOREACH(cac, &caclients, cac_link)
+    cac->cac_save = 0;
+  TAILQ_FOREACH(cac, &caclients, cac_link) {
+    if (cac->cac_index != i) {
+      cac->cac_index = i;
+      cac->cac_save = 1;
+    }
+    i++;
+  }
+  TAILQ_FOREACH(cac, &caclients, cac_link)
+    if (cac->cac_save) {
+      cac->cac_save = 0;
+      caclient_class_save((idnode_t *)cac);
+    }
+}
+
+static int
+cac_cmp(caclient_t *a, caclient_t *b)
+{
+  return a->cac_index - b->cac_index;
+}
+
+caclient_t *
+caclient_create
+  (const char *uuid, htsmsg_t *conf, int save)
+{
+  caclient_t *cac = NULL;
+  const idclass_t *c = NULL;
+  const char *s;
+
+  lock_assert(&global_lock);
+
+  if ((s = htsmsg_get_str(conf, "class")) != NULL)
+    c = caclient_class_find(s);
+  if (c == NULL) {
+    tvherror("caclient", "wrong class %s!", s);
+    abort();
+  }
+#if ENABLE_CWC
+  if (c == &caclient_cwc_class)
+    cac = cwc_create();
+#endif
+#if ENABLE_CAPMT
+  if (c == &caclient_capmt_class)
+    cac = capmt_create();
+#endif
+  if (cac == NULL)
+    abort();
+  if (cac == NULL) {
+    tvherror("caclient", "CA Client class %s is not available!", s);
+    return NULL;
+  }
+  if (idnode_insert(&cac->cac_id, uuid, c, 0)) {
+    if (uuid)
+      tvherror("caclient", "invalid uuid '%s'", uuid);
+    free(cac);
+    return NULL;
+  }
+  if (conf)
+    idnode_load(&cac->cac_id, conf);
+  pthread_mutex_lock(&caclients_mutex);
+  if (cac->cac_index) {
+    TAILQ_INSERT_SORTED(&caclients, cac, cac_link, cac_cmp);
+  } else {
+    TAILQ_INSERT_TAIL(&caclients, cac, cac_link);
+    caclient_reindex();
+  }
+  pthread_mutex_unlock(&caclients_mutex);
+  if (save)
+    caclient_class_save((idnode_t *)cac);
+  cac->cac_conf_changed(cac);
+  return cac;
+}
+
+static void
+caclient_delete(caclient_t *cac, int delconf)
+{
+  cac->cac_enabled = 0;
+  cac->cac_conf_changed(cac);
+  if (delconf)
+    hts_settings_remove("caclient/%s", idnode_uuid_as_str(&cac->cac_id));
+  pthread_mutex_lock(&caclients_mutex);
+  TAILQ_REMOVE(&caclients, cac, cac_link);
+  pthread_mutex_unlock(&caclients_mutex);
+  idnode_unlink(&cac->cac_id);
+  if (cac->cac_free)
+    cac->cac_free(cac);
+  free(cac->cac_name);
+  free(cac->cac_comment);
+  free(cac);
+}
+
+static void
+caclient_class_save ( idnode_t *in )
+{
+  htsmsg_t *c = htsmsg_create_map();
+  idnode_save(in, c);
+  hts_settings_save(c, "caclient/%s", idnode_uuid_as_str(in));
+  htsmsg_destroy(c);
+}
+
+static const char *
+caclient_class_get_title ( idnode_t *in )
+{
+  caclient_t *cac = (caclient_t *)in;
+  static char buf[32];
+  if (cac->cac_name && cac->cac_name[0])
+    return cac->cac_name;
+  snprintf(buf, sizeof(buf), "CA Client %i", cac->cac_index);
+  return buf;
+}
+
+static void
+caclient_class_delete(idnode_t *self)
+{
+  caclient_t *cac = (caclient_t *)self;
+  caclient_delete(cac, 1);
+}
+
+static void
+caclient_class_moveup(idnode_t *self)
+{
+  caclient_t *cac = (caclient_t *)self;
+  caclient_t *prev = TAILQ_PREV(cac, caclient_entry_queue, cac_link);
+  if (prev) {
+    TAILQ_REMOVE(&caclients, cac, cac_link);
+    TAILQ_INSERT_BEFORE(prev, cac, cac_link);
+    caclient_reindex();
+  }
+}
+
+static void
+caclient_class_movedown(idnode_t *self)
+{
+  caclient_t *cac = (caclient_t *)self;
+  caclient_t *next = TAILQ_NEXT(cac, cac_link);
+  if (next) {
+    TAILQ_REMOVE(&caclients, cac, cac_link);
+    TAILQ_INSERT_AFTER(&caclients, next, cac, cac_link);
+    caclient_reindex();
+  }
+}
+
+static const void *
+caclient_class_class_get(void *o)
+{
+  caclient_t *cac = o;
+  static const char *ret;
+  ret = cac->cac_id.in_class->ic_class;
+  return &ret;
+}
+
+static int
+caclient_class_class_set(void *o, const void *v)
+{
+  /* just ignore, create fcn does the right job */
+  return 0;
+}
+
+const idclass_t caclient_class =
+{
+  .ic_class      = "caclient",
+  .ic_caption    = "Conditional Access Client",
+  .ic_save       = caclient_class_save,
+  .ic_event      = "caclient",
+  .ic_get_title  = caclient_class_get_title,
+  .ic_delete     = caclient_class_delete,
+  .ic_moveup     = caclient_class_moveup,
+  .ic_movedown   = caclient_class_movedown,
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_STR,
+      .id       = "class",
+      .name     = "Class",
+      .opts     = PO_RDONLY | PO_HIDDEN,
+      .get      = caclient_class_class_get,
+      .set      = caclient_class_class_set,
+    },
+    {
+      .type     = PT_INT,
+      .id       = "index",
+      .name     = "Index",
+      .opts     = PO_RDONLY | PO_HIDDEN,
+      .off      = offsetof(caclient_t, cac_index),
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "enabled",
+      .name     = "Enabled",
+      .off      = offsetof(caclient_t, cac_enabled),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "name",
+      .name     = "Client Name",
+      .off      = offsetof(caclient_t, cac_name),
+      .notify   = idnode_notify_title_changed,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "comment",
+      .name     = "Comment",
+      .off      = offsetof(caclient_t, cac_comment),
+    },
+    { }
+  }
+};
+
+void
+caclient_start ( struct service *t )
+{
+  caclient_t *cac;
+
+  pthread_mutex_lock(&caclients_mutex);
+  TAILQ_FOREACH(cac, &caclients, cac_link)
+    if (cac->cac_enabled)
+      cac->cac_start(cac, t);
+  pthread_mutex_unlock(&caclients_mutex);
+}
+
+void
+caclient_caid_update(struct mpegts_mux *mux, uint16_t caid, uint16_t pid, int valid)
+{
+  caclient_t *cac;
+
+  lock_assert(&global_lock);
+
+  pthread_mutex_lock(&caclients_mutex);
+  TAILQ_FOREACH(cac, &caclients, cac_link)
+    if (cac->cac_caid_update && cac->cac_enabled)
+      cac->cac_caid_update(cac, mux, caid, pid, valid);
+  pthread_mutex_unlock(&caclients_mutex);
+}
+
+/*
+ *  Initialize
+ */
+void
+caclient_init(void)
+{
+  htsmsg_t *c, *e;
+  htsmsg_field_t *f;
+
+  pthread_mutex_init(&caclients_mutex, NULL);
+  TAILQ_INIT(&caclients);
+
+  if (!(c = hts_settings_load("caclient")))
+    return;
+  HTSMSG_FOREACH(f, c) {
+    if (!(e = htsmsg_field_get_map(f)))
+      continue;
+    caclient_create(f->hmf_name, e, 0);
+  }
+  htsmsg_destroy(c);
+}
+
+void
+caclient_done(void)
+{
+  caclient_t *cac;
+
+  pthread_mutex_lock(&global_lock);
+  while ((cac = TAILQ_FIRST(&caclients)) != NULL)
+    caclient_delete(cac, 0);
+  pthread_mutex_unlock(&global_lock);
+}
diff --git a/src/descrambler/caclient.h b/src/descrambler/caclient.h
new file mode 100644 (file)
index 0000000..0e26b78
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  tvheadend, Conditional Access Client
+ *  Copyright (C) 2014 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
+ *  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 __TVH_CACLIENT_H__
+#define __TVH_CACLIENT_H__
+
+#include "tvheadend.h"
+#include "idnode.h"
+
+struct mpegts_mux;
+
+extern const idclass_t caclient_class;
+extern const idclass_t caclient_cwc_class;
+extern const idclass_t caclient_capmt_class;
+
+TAILQ_HEAD(caclient_entry_queue, caclient);
+
+extern struct caclient_entry_queue caclients;
+
+extern const idclass_t *caclient_classes[];
+
+typedef struct caclient {
+  idnode_t cac_id;
+  TAILQ_ENTRY(caclient) cac_link;
+
+  int cac_save;
+  int cac_index;
+  int cac_enabled;
+  char *cac_name;
+  char *cac_comment;
+
+  void (*cac_free)(struct caclient *cac);
+  void (*cac_start)(struct caclient *cac, struct service *t);
+  void (*cac_conf_changed)(struct caclient *cac);
+  void (*cac_caid_update)(struct caclient *cac,
+                          struct mpegts_mux *mux,
+                          uint16_t caid, uint16_t pid, int valid);
+} caclient_t;
+
+caclient_t *caclient_create
+  (const char *uuid, htsmsg_t *conf, int save);
+
+void caclient_start( struct service *t );
+void caclient_caid_update(struct mpegts_mux *mux,
+                          uint16_t caid, uint16_t pid, int valid);
+
+void caclient_init(void);
+void caclient_done(void);
+
+caclient_t *cwc_create(void);
+caclient_t *capmt_create(void);
+
+#endif /* __TVH_CACLIENT_H__ */
index cd48b8da85752ad833502148f95dede81e002f9c..289b97b9e55929bc78298d0580d664d9aef1c902 100644 (file)
 
 #include "tvheadend.h"
 
-#if ENABLE_CAPMT
-
 #include "input.h"
+#include "caclient.h"
 #include "service.h"
 #include "tcp.h"
 #include "tvhpoll.h"
-#include "capmt.h"
 
 #include "notify.h"
 #include "subscriptions.h"
-#include "dtable.h"
 #include "tvhcsa.h"
 #if ENABLE_LINUXDVB
 #include "input/mpegts/linuxdvb/linuxdvb_private.h"
@@ -113,14 +110,19 @@ typedef struct dmx_filter {
 #define MAX_SOCKETS 16   // max sockets (simultaneous channels) per demux
 #define MAX_PIDS    64   // max opened pids
 
+typedef enum {
+    CAPMT_OSCAM_SO_WRAPPER,
+    CAPMT_OSCAM_OLD,
+    CAPMT_OSCAM_MULTILIST,
+    CAPMT_OSCAM_TCP,
+    CAPMT_OSCAM_UNIX_SOCKET
+} capmt_oscam_mode_t;
+
 /**
  *
  */
-TAILQ_HEAD(capmt_queue, capmt);
 LIST_HEAD(capmt_service_list, capmt_service);
 LIST_HEAD(capmt_caid_ecm_list, capmt_caid_ecm);
-static struct capmt_queue capmts;
-static pthread_cond_t capmt_config_changed;
 
 /*
  * ECM descriptor
@@ -239,9 +241,9 @@ typedef struct capmt_adapter {
  *
  */
 typedef struct capmt {
-  pthread_cond_t capmt_cond;
+  caclient_t;
 
-  TAILQ_ENTRY(capmt) capmt_link; /* Linkage protected via global_lock */
+  pthread_cond_t capmt_cond;
 
   struct capmt_service_list capmt_services;
 
@@ -250,15 +252,7 @@ typedef struct capmt {
   /* from capmt configuration */
   char *capmt_sockfile;
   int   capmt_port;
-  char *capmt_comment;
-  char *capmt_id;
-  enum {
-    CAPMT_OSCAM_SO_WRAPPER,
-    CAPMT_OSCAM_OLD,
-    CAPMT_OSCAM_MULTILIST,
-    CAPMT_OSCAM_TCP,
-    CAPMT_OSCAM_UNIX_SOCKET
-  } capmt_oscam;
+  int   capmt_oscam;
 
   /* capmt sockets */
   int   sids[MAX_SOCKETS];
@@ -290,26 +284,6 @@ static void capmt_send_request(capmt_service_t *ct, int lm);
 static void capmt_table_input(void *opaque, int pid,
                               const uint8_t *data, int len);
 
-/**
- *
- */
-static htsmsg_t *
-capmt_record_build(capmt_t *capmt)
-{
-  htsmsg_t *e = htsmsg_create_map();
-
-  htsmsg_add_str(e, "id", capmt->capmt_id);
-  htsmsg_add_u32(e, "enabled",  !!capmt->capmt_enabled);
-  htsmsg_add_u32(e, "connected", capmt->capmt_connected);
-
-  htsmsg_add_str(e, "camdfilename", capmt->capmt_sockfile ?: "");
-  htsmsg_add_u32(e, "port", capmt->capmt_port);
-  htsmsg_add_u32(e, "oscam", capmt->capmt_oscam);
-  htsmsg_add_str(e, "comment", capmt->capmt_comment ?: "");
-  
-  return e;
-}
-
 /**
  *
  */
@@ -456,10 +430,14 @@ capmt_pid_flush(capmt_t *capmt)
 static void
 capmt_set_connected(capmt_t *capmt, int c)
 {
+  htsmsg_t *m;
   if (c == capmt->capmt_connected)
     return;
+  m = htsmsg_create_map();
   capmt->capmt_connected = c;
-  notify_by_msg("capmt", capmt_record_build(capmt));
+  htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&capmt->cac_id));
+  htsmsg_add_u32(m, "connected", capmt->capmt_connected);
+  notify_by_msg("caclient_status", m);
 }
 
 /*
@@ -1428,12 +1406,12 @@ capmt_thread(void *aux)
     else
       capmt_set_connected(capmt, 0);
     
-    pthread_mutex_lock(&global_lock);
+    pthread_mutex_lock(&capmt->capmt_mutex);
 
     while(capmt->capmt_running && capmt->capmt_enabled == 0)
-      pthread_cond_wait(&capmt->capmt_cond, &global_lock);
+      pthread_cond_wait(&capmt->capmt_cond, &capmt->capmt_mutex);
 
-    pthread_mutex_unlock(&global_lock);
+    pthread_mutex_unlock(&capmt->capmt_mutex);
 
     if (!capmt->capmt_running) continue;
 
@@ -1511,7 +1489,7 @@ capmt_thread(void *aux)
     tvhlog(LOG_INFO, "capmt", "Automatic reconnection attempt in in %d seconds", d);
 
     pthread_mutex_lock(&global_lock);
-    pthread_cond_timedwait(&capmt_config_changed, &global_lock, &ts);
+    pthread_cond_timedwait(&capmt->capmt_cond, &global_lock, &ts);
     pthread_mutex_unlock(&global_lock);
   }
 
@@ -1782,10 +1760,10 @@ capmt_enumerate_services(capmt_t *capmt, int force)
  *
  * global_lock is held
  */
-void
-capmt_service_start(service_t *s)
+static void
+capmt_service_start(caclient_t *cac, service_t *s)
 {
-  capmt_t *capmt;
+  capmt_t *capmt = (capmt_t *)cac;
   capmt_service_t *ct;
   capmt_caid_ecm_t *cce;
   th_descrambler_t *td;
@@ -1809,109 +1787,108 @@ capmt_service_start(service_t *s)
     tuner = lfe->lfe_adapter->la_dvb_number;
 #endif
 
-  TAILQ_FOREACH(capmt, &capmts, capmt_link) {
-    LIST_FOREACH(ct, &capmt->capmt_services, ct_link) {
-      /* skip, if we already have this service */
-      if (ct->td_service == (service_t *)t)
-        return;
+  pthread_mutex_lock(&capmt->capmt_mutex);
+
+  LIST_FOREACH(ct, &capmt->capmt_services, ct_link) {
+    /* skip, if we already have this service */
+    if (ct->td_service == (service_t *)t) {
+      pthread_mutex_unlock(&capmt->capmt_mutex);
+      return;
     }
   }
 
-  TAILQ_FOREACH(capmt, &capmts, capmt_link) {
-    /* skip, if we're not active */
-    if (!capmt->capmt_enabled)
-      continue;
-
-    if (tuner < 0 && capmt->capmt_oscam != CAPMT_OSCAM_TCP &&
-                     capmt->capmt_oscam != CAPMT_OSCAM_UNIX_SOCKET) {
-      tvhlog(LOG_WARNING, "capmt",
-             "Virtual adapters are supported only in modes 3 and 4 (service \"%s\")",
-             t->s_dvb_svcname);
-      continue;
-    }
+  if (tuner < 0 && capmt->capmt_oscam != CAPMT_OSCAM_TCP &&
+                   capmt->capmt_oscam != CAPMT_OSCAM_UNIX_SOCKET) {
+    tvhlog(LOG_WARNING, "capmt",
+           "Virtual adapters are supported only in modes 3 and 4 (service \"%s\")",
+           t->s_dvb_svcname);
+    pthread_mutex_unlock(&capmt->capmt_mutex);
+    return;
+  }
 
-    if (tuner < 0) {
-      for (i = 0; i < MAX_CA; i++) {
-        if (capmt->capmt_adapters[i].ca_tuner == NULL && tuner < 0)
-          tuner = i;
-        if (capmt->capmt_adapters[i].ca_tuner == t->s_dvb_active_input) {
-          tuner = i;
-          break;
-        }
-      }
-      if (tuner < 0 || tuner >= MAX_CA) {
-        tvhlog(LOG_ERR, "capmt",
-               "No free adapter slot available for service \"%s\"",
-               t->s_dvb_svcname);
-        continue;
+  if (tuner < 0) {
+    for (i = 0; i < MAX_CA; i++) {
+      if (capmt->capmt_adapters[i].ca_tuner == NULL && tuner < 0)
+        tuner = i;
+      if (capmt->capmt_adapters[i].ca_tuner == t->s_dvb_active_input) {
+        tuner = i;
+        break;
       }
     }
+    if (tuner < 0 || tuner >= MAX_CA) {
+      tvhlog(LOG_ERR, "capmt",
+             "No free adapter slot available for service \"%s\"",
+             t->s_dvb_svcname);
+      pthread_mutex_unlock(&capmt->capmt_mutex);
+      return;
+    }
+  }
 
-    tvhlog(LOG_INFO, "capmt",
-           "Starting CAPMT server for service \"%s\" on adapter %d seq 0x%04x",
-           t->s_dvb_svcname, tuner, capmt->capmt_seq);
+  tvhlog(LOG_INFO, "capmt",
+         "Starting CAPMT server for service \"%s\" on adapter %d seq 0x%04x",
+         t->s_dvb_svcname, tuner, capmt->capmt_seq);
 
-    capmt->capmt_adapters[tuner].ca_tuner = t->s_dvb_active_input;
+  capmt->capmt_adapters[tuner].ca_tuner = t->s_dvb_active_input;
 
-    /* create new capmt service */
-    ct              = calloc(1, sizeof(capmt_service_t));
-    ct->ct_capmt    = capmt;
-    ct->ct_seq      = capmt->capmt_seq;
-    ct->ct_adapter  = tuner;
+  /* create new capmt service */
+  ct              = calloc(1, sizeof(capmt_service_t));
+  ct->ct_capmt    = capmt;
+  ct->ct_seq      = capmt->capmt_seq;
+  ct->ct_adapter  = tuner;
 
-    /* update the sequence (oscam calls it pid?) to a safe value */
-    capmt->capmt_seq++;
-    capmt->capmt_seq &= 0x1fff;
-    if (capmt->capmt_seq == 8191 || capmt->capmt_seq == 0)
-      capmt->capmt_seq = 1;
+  /* update the sequence (oscam calls it pid?) to a safe value */
+  capmt->capmt_seq++;
+  capmt->capmt_seq &= 0x1fff;
+  if (capmt->capmt_seq == 8191 || capmt->capmt_seq == 0)
+    capmt->capmt_seq = 1;
 
-    change = 0;
-    pthread_mutex_lock(&t->s_stream_mutex);
-    TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
-      caid_t *c;
-      if (t->s_dvb_prefcapid_lock == 2 &&
-          t->s_dvb_prefcapid != st->es_pid)
+  change = 0;
+  pthread_mutex_lock(&t->s_stream_mutex);
+  TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+    caid_t *c;
+    if (t->s_dvb_prefcapid_lock == 2 &&
+        t->s_dvb_prefcapid != st->es_pid)
+      continue;
+    LIST_FOREACH(c, &st->es_caids, link) {
+      if(c == NULL || c->use == 0)
         continue;
-      LIST_FOREACH(c, &st->es_caids, link) {
-        if(c == NULL || c->use == 0)
-          continue;
 
-        tvhlog(LOG_DEBUG, "capmt",
-          "New caid 0x%04X for service \"%s\"", c->caid, t->s_dvb_svcname);
-
-        /* add it to list */
-        cce             = calloc(1, sizeof(capmt_caid_ecm_t));
-        cce->cce_caid   = c->caid;
-        cce->cce_ecmpid = st->es_pid;
-        cce->cce_providerid = c->providerid;
-        LIST_INSERT_HEAD(&ct->ct_caid_ecm, cce, cce_link);
-        ct->ct_constcw |= c->caid == 0x2600 ? 1 : 0;
-        change = 1;
-      }
+      tvhlog(LOG_DEBUG, "capmt",
+        "New caid 0x%04X for service \"%s\"", c->caid, t->s_dvb_svcname);
+
+      /* add it to list */
+      cce             = calloc(1, sizeof(capmt_caid_ecm_t));
+      cce->cce_caid   = c->caid;
+      cce->cce_ecmpid = st->es_pid;
+      cce->cce_providerid = c->providerid;
+      LIST_INSERT_HEAD(&ct->ct_caid_ecm, cce, cce_link);
+      ct->ct_constcw |= c->caid == 0x2600 ? 1 : 0;
+      change = 1;
     }
-    pthread_mutex_unlock(&t->s_stream_mutex);
+  }
+  pthread_mutex_unlock(&t->s_stream_mutex);
+
+  td = (th_descrambler_t *)ct;
+  tvhcsa_init(td->td_csa = &ct->ct_csa);
+  snprintf(buf, sizeof(buf), "capmt-%s-%i",
+                             capmt->capmt_sockfile,
+                             capmt->capmt_port);
+  td->td_nicename    = strdup(buf);
+  td->td_service     = s;
+  td->td_stop        = capmt_service_destroy;
+  td->td_caid_change = capmt_caid_change;
+  td->td_ecm_reset   = capmt_ecm_reset;
+
+  LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
+  LIST_INSERT_HEAD(&capmt->capmt_services, ct, ct_link);
+
+  /* wake-up idle thread */
+  pthread_cond_signal(&capmt->capmt_cond);
 
-    td = (th_descrambler_t *)ct;
-    tvhcsa_init(td->td_csa = &ct->ct_csa);
-    snprintf(buf, sizeof(buf), "capmt-%s-%i",
-                               capmt->capmt_sockfile,
-                               capmt->capmt_port);
-    td->td_nicename    = strdup(buf);
-    td->td_service     = s;
-    td->td_stop        = capmt_service_destroy;
-    td->td_caid_change = capmt_caid_change;
-    td->td_ecm_reset   = capmt_ecm_reset;
-    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
-
-    LIST_INSERT_HEAD(&capmt->capmt_services, ct, ct_link);
-
-    /* wake-up idle thread */
-    pthread_cond_signal(&capmt->capmt_cond);
-    pthread_cond_signal(&capmt_config_changed);
+  pthread_mutex_unlock(&capmt->capmt_mutex);
 
-    if (change)
-      capmt_notify_server(capmt, NULL, 0);
-  }
+  if (change)
+    capmt_notify_server(capmt, NULL, 0);
 }
 
 
@@ -1919,219 +1896,113 @@ capmt_service_start(service_t *s)
  *
  */
 static void
-capmt_destroy(capmt_t *capmt)
+capmt_free(caclient_t *cac)
 {
-  lock_assert(&global_lock);
-  TAILQ_REMOVE(&capmts, capmt, capmt_link);  
-  capmt->capmt_running = 0;
-  pthread_cond_signal(&capmt->capmt_cond);
-  pthread_mutex_unlock(&global_lock);
-  tvh_write(capmt->capmt_pipe.wr, "", 1);
-  pthread_join(capmt->capmt_tid, NULL);
-  pthread_mutex_lock(&global_lock);
+  capmt_t *capmt = (capmt_t *)cac;
   tvhlog(LOG_INFO, "capmt", "mode %i %s %s port %i destroyed",
          capmt->capmt_oscam,
          capmt->capmt_oscam == CAPMT_OSCAM_TCP ? "IP address" : "sockfile",
          capmt->capmt_sockfile, capmt->capmt_port);
   capmt_flush_queue(capmt, 1);
-  free(capmt->capmt_id);
   free(capmt->capmt_sockfile);
-  free(capmt->capmt_comment);
-  free(capmt);
 }
 
 /**
  *
  */
-static capmt_t *
-capmt_entry_find(const char *id, int create)
+static void
+capmt_conf_changed(caclient_t *cac)
 {
-  char buf[20];
-  capmt_t *capmt;
-  static int tally;
-
-  if(id != NULL) {
-    TAILQ_FOREACH(capmt, &capmts, capmt_link)
-      if(!strcmp(capmt->capmt_id, id))
-  return capmt;
-  }
-  if(create == 0)
-    return NULL;
+  capmt_t *capmt = (capmt_t *)cac;
+  pthread_t tid;
 
-  if(id == NULL) {
-    tally++;
-    snprintf(buf, sizeof(buf), "%d", tally);
-    id = buf;
+  if (capmt->cac_enabled) {
+    if (capmt->capmt_sockfile == NULL || capmt->capmt_sockfile[0] == '\0')
+      return;
+    if (!capmt->capmt_running) {
+      capmt->capmt_running = 1;
+      tvhthread_create(&capmt->capmt_tid, NULL, capmt_thread, capmt);
+      return;
+    }
+    pthread_mutex_lock(&capmt->capmt_mutex);
+    capmt->capmt_reconfigure = 1;
+    pthread_cond_signal(&capmt->capmt_cond);
+    pthread_mutex_unlock(&capmt->capmt_mutex);
   } else {
-    tally = MAX(atoi(id), tally);
-  }
-
-  capmt = calloc(1, sizeof(capmt_t));
-  pthread_mutex_init(&capmt->capmt_mutex, NULL);
-  pthread_cond_init(&capmt->capmt_cond, NULL);
-  capmt->capmt_id      = strdup(id); 
-  capmt->capmt_running = 1; 
-  capmt->capmt_seq     = 1;
-  TAILQ_INIT(&capmt->capmt_writeq);
-
-  TAILQ_INSERT_TAIL(&capmts, capmt, capmt_link);
-
-  tvh_pipe(O_NONBLOCK, &capmt->capmt_pipe);
-
-  tvhthread_create(&capmt->capmt_tid, NULL, capmt_thread, capmt);
-
-  return capmt;
-}
-
-
-/**
- *
- */
-static htsmsg_t *
-capmt_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate)
-{
-  capmt_t *capmt;
-  const char *s;
-  uint32_t u32;
-
-  if((capmt = capmt_entry_find(id, maycreate)) == NULL)
-    return NULL;
-
-  lock_assert(&global_lock);
-  
-  if((s = htsmsg_get_str(values, "camdfilename")) != NULL) {
-    free(capmt->capmt_sockfile);
-    capmt->capmt_sockfile = strdup(s);
-  }
-  
-  if(!htsmsg_get_u32(values, "port", &u32))
-    capmt->capmt_port = u32;
-
-  if(!htsmsg_get_u32(values, "oscam", &u32)) 
-    capmt->capmt_oscam = u32;
-
-  if((s = htsmsg_get_str(values, "comment")) != NULL) {
-    free(capmt->capmt_comment);
-    capmt->capmt_comment = strdup(s);
+    if (!capmt->capmt_running)
+      return;
+    pthread_mutex_lock(&capmt->capmt_mutex);
+    capmt->capmt_running = 0;
+    pthread_cond_signal(&capmt->capmt_cond);
+    tid = capmt->capmt_tid;
+    pthread_mutex_unlock(&capmt->capmt_mutex);
+    tvh_write(capmt->capmt_pipe.wr, "", 1);
+    pthread_join(tid, NULL);
   }
 
-  if(!htsmsg_get_u32(values, "enabled", &u32)) 
-    capmt->capmt_enabled = u32;
-
-
-  capmt->capmt_reconfigure = 1;
-
-  pthread_cond_signal(&capmt->capmt_cond);
-
-  pthread_cond_broadcast(&capmt_config_changed);
-
-  return capmt_record_build(capmt);
-}
-  
-
-
-/**
- *
- */
-static int
-capmt_entry_delete(void *opaque, const char *id)
-{
-  capmt_t *capmt;
-
-  pthread_cond_broadcast(&capmt_config_changed);
-
-  if((capmt = capmt_entry_find(id, 0)) == NULL)
-    return -1;
-  capmt_destroy(capmt);
-  return 0;
 }
 
-
-/**
- *
- */
 static htsmsg_t *
-capmt_entry_get_all(void *opaque)
+caclient_capmt_class_oscam_mode_list ( void *o )
 {
-  htsmsg_t *r = htsmsg_create_list();
-  capmt_t *capmt;
-
-  TAILQ_FOREACH(capmt, &capmts, capmt_link)
-    htsmsg_add_msg(r, NULL, capmt_record_build(capmt));
-
-  return r;
+  static const struct strtab tab[] = {
+    { "OSCam pc-nodmx (rev >= 9756)", CAPMT_OSCAM_UNIX_SOCKET},
+    { "OSCam TCP (rev >= 9574)",      CAPMT_OSCAM_TCP },
+    { "OSCam (rev >= 9095)",          CAPMT_OSCAM_MULTILIST },
+    { "Older OSCam",                  CAPMT_OSCAM_OLD },
+    { "Wrapper (capmt_ca.so)",        CAPMT_OSCAM_SO_WRAPPER },
+  };
+  return strtab2htsmsg(tab);
 }
 
-/**
- *
- */
-static htsmsg_t *
-capmt_entry_get(void *opaque, const char *id)
+const idclass_t caclient_capmt_class =
 {
-  capmt_t *capmt;
-
-
-  if((capmt = capmt_entry_find(id, 0)) == NULL)
-    return NULL;
-  return capmt_record_build(capmt);
-}
-
-/**
- *
- */
-static htsmsg_t *
-capmt_entry_create(void *opaque)
-{
-  pthread_cond_broadcast(&capmt_config_changed);
-  return capmt_record_build(capmt_entry_find(NULL, 1));
-}
-
-/**
- *
- */
-static const dtable_class_t capmt_dtc = {
-  .dtc_record_get     = capmt_entry_get,
-  .dtc_record_get_all = capmt_entry_get_all,
-  .dtc_record_create  = capmt_entry_create,
-  .dtc_record_update  = capmt_entry_update,
-  .dtc_record_delete  = capmt_entry_delete,
-  .dtc_read_access = ACCESS_ADMIN,
-  .dtc_write_access = ACCESS_ADMIN,
-  .dtc_mutex = &global_lock,
+  .ic_super      = &caclient_class,
+  .ic_class      = "caclient_capmt",
+  .ic_caption    = "CAPMT (Linux DVBAPI)",
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_INT,
+      .id       = "mode",
+      .name     = "Mode",
+      .off      = offsetof(capmt_t, capmt_oscam),
+      .list     = caclient_capmt_class_oscam_mode_list,
+      .def.i    = CAPMT_OSCAM_MULTILIST,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "camdfilename",
+      .name     = "Camd.socket Filename / IP Address (mode 3)",
+      .off      = offsetof(capmt_t, capmt_sockfile),
+      .def.s    = "/tmp/camd.socket",
+    },
+    {
+      .type     = PT_U16,
+      .id       = "port",
+      .name     = "Listen/Connect Port",
+      .off      = offsetof(capmt_t, capmt_port),
+      .def.i    = 9000
+    },
+    { }
+  }
 };
 
-/**
+/*
  *
  */
-void
-capmt_init(void)
+caclient_t *capmt_create(void)
 {
-  dtable_t *dt;
-
-  TAILQ_INIT(&capmts);
+  capmt_t *capmt = calloc(1, sizeof(*capmt));
 
-  pthread_cond_init(&capmt_config_changed, NULL);
+  pthread_mutex_init(&capmt->capmt_mutex, NULL);
+  pthread_cond_init(&capmt->capmt_cond, NULL);
+  capmt->capmt_seq = 1;
+  TAILQ_INIT(&capmt->capmt_writeq);
+  tvh_pipe(O_NONBLOCK, &capmt->capmt_pipe);
 
-  dt = dtable_create(&capmt_dtc, "capmt", NULL);
-  dtable_load(dt);
-}
+  capmt->cac_free         = capmt_free;
+  capmt->cac_start        = capmt_service_start;
+  capmt->cac_conf_changed = capmt_conf_changed;
 
-void
-capmt_done(void)
-{
-  capmt_t *capmt;
-
-  dtable_delete("capmt");
-  pthread_mutex_lock(&global_lock);
-  while ((capmt = TAILQ_FIRST(&capmts)) != NULL)
-    capmt_destroy(capmt);
-  pthread_mutex_unlock(&global_lock);
+  return (caclient_t *)capmt;
 }
-
-#else /* ENABLE_CAPMT */
-
-void capmt_init ( void ) {}
-void capmt_done ( void ) {}
-void capmt_service_start(service_t *s) {}
-
-#endif
diff --git a/src/descrambler/capmt.h b/src/descrambler/capmt.h
deleted file mode 100644 (file)
index 711c95d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- *  tvheadend, CAPMT interface
- *  Copyright (C) 2009
- *
- *  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 CAPMT_H_
-#define CAPMT_H_
-
-void capmt_init(void);
-
-void capmt_done(void);
-
-void capmt_service_start(struct service *t);
-
-#endif /* CAPMT_H_ */
index 0f70fc88ed12439d065208e37d42cef0b6b3e5c9..bdfd02623334c28901812aec8978effb3e6f20c1 100755 (executable)
 
 #include "tvheadend.h"
 #include "tcp.h"
-#include "cwc.h"
+#include "caclient.h"
 #include "notify.h"
 #include "atomic.h"
-#include "dtable.h"
 #include "subscriptions.h"
 #include "service.h"
 #include "input.h"
@@ -87,9 +86,6 @@ LIST_HEAD(cwc_cards_list, cs_card_data);
 LIST_HEAD(cwc_service_list, cwc_service);
 TAILQ_HEAD(cwc_message_queue, cwc_message);
 LIST_HEAD(ecm_section_list, ecm_section);
-static struct cwc_queue cwcs;
-static pthread_cond_t cwc_config_changed;
-static pthread_mutex_t cwc_mutex;
 static char *crypt_md5(const char *pw, const char *salt);
 
 /**
@@ -207,6 +203,8 @@ typedef struct cs_card_data {
  *
  */
 typedef struct cwc {
+  caclient_t;
+
   int cwc_fd;
   int cwc_connected;
 
@@ -216,13 +214,12 @@ typedef struct cwc {
 
   pthread_cond_t cwc_cond;
 
+  pthread_mutex_t cwc_mutex;
   pthread_mutex_t cwc_writer_mutex; 
   pthread_cond_t cwc_writer_cond; 
   int cwc_writer_running;
   struct cwc_message_queue cwc_writeq;
 
-  TAILQ_ENTRY(cwc) cwc_link; /* Linkage protected via cwc_mutex */
-
   struct cwc_service_list cwc_services;
 
 
@@ -265,10 +262,8 @@ typedef struct cwc {
   char *cwc_username;
   char *cwc_password;
   char *cwc_password_salted;   /* salted version */
-  char *cwc_comment;
   char *cwc_hostname;
   int cwc_port;
-  char *cwc_id;
   int cwc_emm;
   int cwc_emmex;
 
@@ -558,9 +553,9 @@ cwc_comet_status_update(cwc_t *cwc)
 {
   htsmsg_t *m = htsmsg_create_map();
 
-  htsmsg_add_str(m, "id", cwc->cwc_id);
+  htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&cwc->cac_id));
   htsmsg_add_u32(m, "connected", !!cwc->cwc_connected);
-  notify_by_msg("cwcStatus", m);
+  notify_by_msg("caclient_status", m);
 }
 
 /**
@@ -990,9 +985,9 @@ cwc_read(cwc_t *cwc, void *buf, size_t len, int timeout)
 {
   int r;
 
-  pthread_mutex_unlock(&cwc_mutex);
+  pthread_mutex_unlock(&cwc->cwc_mutex);
   r = tcp_read_timeout(cwc->cwc_fd, buf, len, timeout);
-  pthread_mutex_lock(&cwc_mutex);
+  pthread_mutex_lock(&cwc->cwc_mutex);
 
   if (r && tvheadend_running)
     tvhwarn("cwc", "read error %d (%s)", r, strerror(r));
@@ -1186,36 +1181,28 @@ cwc_session(cwc_t *cwc)
 static void *
 cwc_thread(void *aux)
 {
-  cwc_service_t *ct;
-  cs_card_data_t *cd;
   cwc_t *cwc = aux;
   int fd, d;
   char errbuf[100];
-  mpegts_service_t *t;
   char hostname[256];
   int port;
   struct timespec ts;
   int attempts = 0;
 
-  pthread_mutex_lock(&cwc_mutex);
+  pthread_mutex_lock(&cwc->cwc_mutex);
 
   while(cwc->cwc_running) {
     
-    while(cwc->cwc_running && cwc->cwc_enabled == 0)
-      pthread_cond_wait(&cwc->cwc_cond, &cwc_mutex);
-    if (cwc->cwc_running == 0) continue;
-    if (cwc->cwc_hostname == NULL) continue;
-
     snprintf(hostname, sizeof(hostname), "%s", cwc->cwc_hostname);
     port = cwc->cwc_port;
 
     tvhlog(LOG_INFO, "cwc", "Attemping to connect to %s:%d", hostname, port);
 
-    pthread_mutex_unlock(&cwc_mutex);
+    pthread_mutex_unlock(&cwc->cwc_mutex);
 
     fd = tcp_connect(hostname, port, NULL, errbuf, sizeof(errbuf), 10);
 
-    pthread_mutex_lock(&cwc_mutex);
+    pthread_mutex_lock(&cwc->cwc_mutex);
 
     if(fd == -1) {
       attempts++;
@@ -1256,35 +1243,12 @@ cwc_thread(void *aux)
            "%s:%i: Automatic connection attempt in %d seconds",
            cwc->cwc_hostname, cwc->cwc_port, d-1);
 
-    pthread_cond_timedwait(&cwc_config_changed, &cwc_mutex, &ts);
+    pthread_cond_timedwait(&cwc->cwc_cond, &cwc->cwc_mutex, &ts);
   }
 
-  tvhlog(LOG_INFO, "cwc", "%s:%i destroyed",
+  tvhlog(LOG_INFO, "cwc", "%s:%i inactive",
          cwc->cwc_hostname, cwc->cwc_port);
-
-  while((ct = LIST_FIRST(&cwc->cwc_services)) != NULL) {
-    t = (mpegts_service_t *)ct->td_service;
-    pthread_mutex_lock(&t->s_stream_mutex);
-    cwc_service_destroy((th_descrambler_t *)&ct);
-    pthread_mutex_unlock(&t->s_stream_mutex);
-  }
-
-  while((cd = LIST_FIRST(&cwc->cwc_cards)) != NULL) {
-    LIST_REMOVE(cd, cs_card);
-    descrambler_close_emm(cd->cwc_mux, cd, cd->cwc_caid);
-    free(cd->cwc_providers);
-    free(cd);
-  }
-  free((void *)cwc->cwc_password);
-  free((void *)cwc->cwc_password_salted);
-  free((void *)cwc->cwc_username);
-  free((void *)cwc->cwc_comment);
-  free((void *)cwc->cwc_hostname);
-  free((void *)cwc->cwc_id);
-  free((void *)cwc->cwc_viaccess_emm.shared_emm);
-  free(cwc);
-
-  pthread_mutex_unlock(&cwc_mutex);
+  pthread_mutex_unlock(&cwc->cwc_mutex);
   return NULL;
 }
 
@@ -1346,8 +1310,8 @@ cwc_emm(void *opaque, int pid, const uint8_t *data, int len)
   }
   if (pcard->cwc_mux == NULL)
     return;
-  pthread_mutex_lock(&cwc_mutex);
   cwc          = pcard->cwc;
+  pthread_mutex_lock(&cwc->cwc_mutex);
   ca_update_id = pcard->cwc_mux;
   if (cwc->cwc_forward_emm && cwc->cwc_writer_running) {
     if (cwc->cwc_emmex) {
@@ -1395,7 +1359,7 @@ cwc_emm(void *opaque, int pid, const uint8_t *data, int len)
     }
   }
 end_of_job:
-  pthread_mutex_unlock(&cwc_mutex);
+  pthread_mutex_unlock(&cwc->cwc_mutex);
 }
 
 
@@ -2048,10 +2012,10 @@ cwc_service_destroy(th_descrambler_t *td)
  *
  * global_lock is held. Not that we care about that, but either way, it is.
  */
-void
-cwc_service_start(service_t *t)
+static void
+cwc_service_start(caclient_t *cac, service_t *t)
 {
-  cwc_t *cwc;
+  cwc_t *cwc = (cwc_t *)cac;
   cwc_service_t *ct;
   th_descrambler_t *td;
   elementary_stream_t *st;
@@ -2064,78 +2028,80 @@ cwc_service_start(service_t *t)
   if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
     return;
 
-  pthread_mutex_lock(&cwc_mutex);
-  TAILQ_FOREACH(cwc, &cwcs, cwc_link) {
-    LIST_FOREACH(ct, &cwc->cwc_services, cs_link) {
-      if (ct->td_service == t && ct->cs_cwc == cwc)
-        break;
-    }
-    pthread_mutex_lock(&t->s_stream_mutex);
-    LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) {
-      if (pcard->cwc_caid == 0) continue;
-      TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
-        if (((mpegts_service_t *)t)->s_dvb_prefcapid_lock == 2 &&
-            ((mpegts_service_t *)t)->s_dvb_prefcapid != st->es_pid)
-          continue;
-        LIST_FOREACH(c, &st->es_caids, link) {
-          if (c->use && c->caid == pcard->cwc_caid)
-            break;
-        }
-        if (c) break;
-      }
-      if (st) break;
-    }
-    if (!pcard) {
-      if (ct) cwc_service_destroy((th_descrambler_t*)ct);
-      pthread_mutex_unlock(&t->s_stream_mutex);
-      continue;
-    }
-
-    pthread_mutex_unlock(&t->s_stream_mutex);
-    if (ct) continue;
-
-    ct                   = calloc(1, sizeof(cwc_service_t));
-    ct->cs_cwc           = cwc;
-    ct->cs_channel       = -1;
-    ct->cs_mux           = ((mpegts_service_t *)t)->s_dvb_mux;
-    ct->ecm_state        = ECM_INIT;
-    ct->cs_constcw       = pcard->cwc_caid == 0x2600;
-
-    td                   = (th_descrambler_t *)ct;
-    tvhcsa_init(td->td_csa = &ct->cs_csa);
-    snprintf(buf, sizeof(buf), "cwc-%s-%i", cwc->cwc_hostname, cwc->cwc_port);
-    td->td_nicename      = strdup(buf);
-    td->td_service       = t;
-    td->td_stop          = cwc_service_destroy;
-    td->td_ecm_reset     = cwc_ecm_reset;
-    td->td_ecm_idle      = cwc_ecm_idle;
-    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
-
-    LIST_INSERT_HEAD(&cwc->cwc_services, ct, cs_link);
-
-    pthread_mutex_lock(&t->s_stream_mutex);
-    i = 0;
+  pthread_mutex_lock(&cwc->cwc_mutex);
+  LIST_FOREACH(ct, &cwc->cwc_services, cs_link) {
+    if (ct->td_service == t && ct->cs_cwc == cwc)
+      break;
+  }
+  pthread_mutex_lock(&t->s_stream_mutex);
+  LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) {
+    if (pcard->cwc_caid == 0) continue;
     TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
-      LIST_FOREACH(c, &st->es_caids, link)
-        if (c->use && c->caid == pcard->cwc_caid) {
-          ct->cs_epids[i++] = st->es_pid;
+      if (((mpegts_service_t *)t)->s_dvb_prefcapid_lock == 2 &&
+          ((mpegts_service_t *)t)->s_dvb_prefcapid != st->es_pid)
+        continue;
+      LIST_FOREACH(c, &st->es_caids, link) {
+        if (c->use && c->caid == pcard->cwc_caid)
           break;
-        }
-      if (i == CWC_ES_PIDS) break;
+      }
+      if (c) break;
     }
+    if (st) break;
+  }
+  if (!pcard) {
+    if (ct) cwc_service_destroy((th_descrambler_t*)ct);
     pthread_mutex_unlock(&t->s_stream_mutex);
+    pthread_mutex_unlock(&cwc->cwc_mutex);
+    return;
+  }
 
-    for (i = 0; i < CWC_ES_PIDS; i++)
-      if (ct->cs_epids[i])
-        descrambler_open_pid(ct->cs_mux, ct,
-                             DESCRAMBLER_ECM_PID(ct->cs_epids[i]),
-                             cwc_table_input, t);
+  pthread_mutex_unlock(&t->s_stream_mutex);
+  if (ct) {
+    pthread_mutex_unlock(&cwc->cwc_mutex);
+    return;
+  }
 
-    tvhlog(LOG_DEBUG, "cwc", "%s using CWC %s:%d",
-           service_nicename(t), cwc->cwc_hostname, cwc->cwc_port);
+  ct                   = calloc(1, sizeof(cwc_service_t));
+  ct->cs_cwc           = cwc;
+  ct->cs_channel       = -1;
+  ct->cs_mux           = ((mpegts_service_t *)t)->s_dvb_mux;
+  ct->ecm_state        = ECM_INIT;
+  ct->cs_constcw       = pcard->cwc_caid == 0x2600;
+
+  td                   = (th_descrambler_t *)ct;
+  tvhcsa_init(td->td_csa = &ct->cs_csa);
+  snprintf(buf, sizeof(buf), "cwc-%s-%i", cwc->cwc_hostname, cwc->cwc_port);
+  td->td_nicename      = strdup(buf);
+  td->td_service       = t;
+  td->td_stop          = cwc_service_destroy;
+  td->td_ecm_reset     = cwc_ecm_reset;
+  td->td_ecm_idle      = cwc_ecm_idle;
+  LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
+
+  LIST_INSERT_HEAD(&cwc->cwc_services, ct, cs_link);
 
+  pthread_mutex_lock(&t->s_stream_mutex);
+  i = 0;
+  TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+    LIST_FOREACH(c, &st->es_caids, link)
+      if (c->use && c->caid == pcard->cwc_caid) {
+        ct->cs_epids[i++] = st->es_pid;
+        break;
+      }
+    if (i == CWC_ES_PIDS) break;
   }
-  pthread_mutex_unlock(&cwc_mutex);
+  pthread_mutex_unlock(&t->s_stream_mutex);
+
+  for (i = 0; i < CWC_ES_PIDS; i++)
+    if (ct->cs_epids[i])
+      descrambler_open_pid(ct->cs_mux, ct,
+                           DESCRAMBLER_ECM_PID(ct->cs_epids[i]),
+                           cwc_table_input, t);
+
+  tvhlog(LOG_DEBUG, "cwc", "%s using CWC %s:%d",
+         service_nicename(t), cwc->cwc_hostname, cwc->cwc_port);
+
+  pthread_mutex_unlock(&cwc->cwc_mutex);
 }
 
 
@@ -2143,139 +2109,64 @@ cwc_service_start(service_t *t)
  *
  */
 static void
-cwc_destroy(cwc_t *cwc)
+cwc_free(caclient_t *cac)
 {
-  pthread_t tid;
+  cwc_t *cwc = (cwc_t *)cac;
+  cwc_service_t *ct;
+  cs_card_data_t *cd;
+  mpegts_service_t *t;
+
+  while((ct = LIST_FIRST(&cwc->cwc_services)) != NULL) {
+    t = (mpegts_service_t *)ct->td_service;
+    pthread_mutex_lock(&t->s_stream_mutex);
+    cwc_service_destroy((th_descrambler_t *)&ct);
+    pthread_mutex_unlock(&t->s_stream_mutex);
+  }
 
-  lock_assert(&cwc_mutex);
-  TAILQ_REMOVE(&cwcs, cwc, cwc_link);  
-  cwc->cwc_running = 0;
-  pthread_cond_signal(&cwc->cwc_cond);
-  tid = cwc->cwc_tid;
-  pthread_mutex_unlock(&cwc_mutex);
-  pthread_kill(tid, SIGTERM);
-  pthread_join(tid, NULL);
-  pthread_mutex_lock(&cwc_mutex);
+  while((cd = LIST_FIRST(&cwc->cwc_cards)) != NULL) {
+    LIST_REMOVE(cd, cs_card);
+    descrambler_close_emm(cd->cwc_mux, cd, cd->cwc_caid);
+    free(cd->cwc_providers);
+    free(cd);
+  }
+  free((void *)cwc->cwc_password);
+  free((void *)cwc->cwc_password_salted);
+  free((void *)cwc->cwc_username);
+  free((void *)cwc->cwc_hostname);
+  free((void *)cwc->cwc_viaccess_emm.shared_emm);
 }
 
 /**
  *
  */
-void
-cwc_caid_update(mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid)
+static void
+cwc_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid)
 {
-  cwc_t *cwc;
+  cwc_t *cwc = (cwc_t *)cac;;
   struct cs_card_data *pcard;
 
   tvhtrace("cwc",
-           "caid update event - mux %p caid %04x (%i) pid %04x (%i) valid %i",
-           mux, caid, caid, pid, pid, valid);
-  pthread_mutex_lock(&cwc_mutex);
-  TAILQ_FOREACH(cwc, &cwcs, cwc_link) {
-    if (valid < 0 || cwc->cwc_running) {
-      LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) {
-        if (valid < 0 || pcard->cwc_caid == caid) {
-          if (pcard->cwc_mux && pcard->cwc_mux != mux) continue;
-          if (valid > 0) {
-            pcard->cwc       = cwc;
-            pcard->cwc_mux   = mux;
-            descrambler_open_emm(mux, pcard, caid, cwc_emm);
-          } else {
-            pcard->cwc_mux   = NULL;
-            descrambler_close_emm(mux, pcard, caid);
-          }
+           "caid update event - client %s mux %p caid %04x (%i) pid %04x (%i) valid %i",
+           cac->cac_name, mux, caid, caid, pid, pid, valid);
+  pthread_mutex_lock(&cwc->cwc_mutex);
+  if (valid < 0 || cwc->cwc_running) {
+    LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) {
+      if (valid < 0 || pcard->cwc_caid == caid) {
+        if (pcard->cwc_mux && pcard->cwc_mux != mux) continue;
+        if (valid > 0) {
+          pcard->cwc       = cwc;
+          pcard->cwc_mux   = mux;
+          descrambler_open_emm(mux, pcard, caid, cwc_emm);
+        } else {
+          pcard->cwc_mux   = NULL;
+          descrambler_close_emm(mux, pcard, caid);
         }
       }
     }
   }
-  pthread_mutex_unlock(&cwc_mutex);
-}
-
-
-/**
- *
- */
-static cwc_t *
-cwc_entry_find(const char *id, int create)
-{
-  char buf[20];
-  cwc_t *cwc;
-  static int tally;
-
-  if(id != NULL) {
-    TAILQ_FOREACH(cwc, &cwcs, cwc_link) {
-      if(!strcmp(cwc->cwc_id, id))
-        return cwc;
-    }
-  }
-  if(create == 0)
-    return NULL;
-
-  if(id == NULL) {
-    tally++;
-    snprintf(buf, sizeof(buf), "%d", tally);
-    id = buf;
-  } else {
-    tally = MAX(atoi(id), tally);
-  }
-
-  cwc = calloc(1, sizeof(cwc_t));
-  pthread_cond_init(&cwc->cwc_cond, NULL);
-  cwc->cwc_id = strdup(id); 
-  cwc->cwc_running = 1;
-  TAILQ_INSERT_TAIL(&cwcs, cwc, cwc_link);  
-
-  tvhthread_create(&cwc->cwc_tid, NULL, cwc_thread, cwc);
-
-  return cwc;
+  pthread_mutex_unlock(&cwc->cwc_mutex);
 }
 
-
-/**
- *
- */
-static htsmsg_t *
-cwc_record_build(cwc_t *cwc)
-{
-  htsmsg_t *e = htsmsg_create_map();
-  char buf[100];
-
-  htsmsg_add_str(e, "id", cwc->cwc_id);
-  htsmsg_add_u32(e, "enabled",  !!cwc->cwc_enabled);
-  htsmsg_add_u32(e, "connected", !!cwc->cwc_connected);
-
-  htsmsg_add_str(e, "hostname", cwc->cwc_hostname ?: "");
-  htsmsg_add_u32(e, "port", cwc->cwc_port);
-
-  htsmsg_add_str(e, "username", cwc->cwc_username ?: "");
-  htsmsg_add_str(e, "password", cwc->cwc_password ?: "");
-  snprintf(buf, sizeof(buf),
-           "%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
-           "%02x:%02x:%02x:%02x:%02x:%02x:%02x",
-           cwc->cwc_confedkey[0x0],
-           cwc->cwc_confedkey[0x1],
-           cwc->cwc_confedkey[0x2],
-           cwc->cwc_confedkey[0x3],
-           cwc->cwc_confedkey[0x4],
-           cwc->cwc_confedkey[0x5],
-           cwc->cwc_confedkey[0x6],
-           cwc->cwc_confedkey[0x7],
-           cwc->cwc_confedkey[0x8],
-           cwc->cwc_confedkey[0x9],
-           cwc->cwc_confedkey[0xa],
-           cwc->cwc_confedkey[0xb],
-           cwc->cwc_confedkey[0xc],
-           cwc->cwc_confedkey[0xd]);
-  
-  htsmsg_add_str(e, "deskey", buf);
-  htsmsg_add_u32(e, "emm", cwc->cwc_emm);
-  htsmsg_add_u32(e, "emmex", cwc->cwc_emmex);
-  htsmsg_add_str(e, "comment", cwc->cwc_comment ?: "");
-
-  return e;
-}
-
-
 /**
  *
  */
@@ -2294,197 +2185,175 @@ nibble(char c)
   }
 }
 
-
 /**
  *
  */
-static htsmsg_t *
-cwc_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate)
+static void
+cwc_conf_changed(caclient_t *cac)
 {
-  cwc_t *cwc;
-  const char *s;
-  uint32_t u32;
-  uint8_t key[14];
-  int u, l, i;
-
-  if((cwc = cwc_entry_find(id, maycreate)) == NULL)
-    return NULL;
-
-  if((s = htsmsg_get_str(values, "username")) != NULL) {
-    free(cwc->cwc_username);
-    cwc->cwc_username = strdup(s);
-  }
-
-  if((s = htsmsg_get_str(values, "password")) != NULL) {
-    free(cwc->cwc_password);
-    free(cwc->cwc_password_salted);
-    cwc->cwc_password = strdup(s);
-    cwc->cwc_password_salted = crypt_md5(s, "$1$abcdefgh$");
-  }
-
-  if((s = htsmsg_get_str(values, "comment")) != NULL) {
-    free(cwc->cwc_comment);
-    cwc->cwc_comment = strdup(s);
-  }
-
-  if((s = htsmsg_get_str(values, "hostname")) != NULL) {
-    free(cwc->cwc_hostname);
-    cwc->cwc_hostname = strdup(s);
-  }
-
-  if(!htsmsg_get_u32(values, "enabled", &u32))
-    cwc->cwc_enabled = u32;
+  cwc_t *cwc = (cwc_t *)cac;
+  pthread_t tid;
 
-  if(!htsmsg_get_u32(values, "port", &u32))
-    cwc->cwc_port = u32;
+  free(cwc->cwc_password_salted);
+  cwc->cwc_password_salted =
+    cwc->cwc_password ? crypt_md5(cwc->cwc_password, "$1$abcdefgh$") : NULL;
 
-  if((s = htsmsg_get_str(values, "deskey")) != NULL) {
-    for(i = 0; i < 14; i++) {
-      while(*s != 0 && !isxdigit(*s)) s++;
-      if(*s == 0)
-        break;
-      u = nibble(*s++);
-      while(*s != 0 && !isxdigit(*s)) s++;
-      if(*s == 0)
-        break;
-      l = nibble(*s++);
-      key[i] = (u << 4) | l;
+  if (cac->cac_enabled) {
+    if (cwc->cwc_hostname == NULL || cwc->cwc_hostname[0] == '\0')
+      return;
+    if (!cwc->cwc_running) {
+      cwc->cwc_running = 1;
+      tvhthread_create(&cwc->cwc_tid, NULL, cwc_thread, cwc);
+      return;
     }
-    memcpy(cwc->cwc_confedkey, key, 14);
+    pthread_mutex_lock(&cwc->cwc_mutex);
+    cwc->cwc_reconfigure = 1;
+    if(cwc->cwc_fd != -1)
+      shutdown(cwc->cwc_fd, SHUT_RDWR);
+    pthread_cond_signal(&cwc->cwc_cond);
+    pthread_mutex_unlock(&cwc->cwc_mutex);
+  } else {
+    if (!cwc->cwc_running)
+      return;
+    pthread_mutex_lock(&cwc->cwc_mutex);
+    cwc->cwc_running = 0;
+    pthread_cond_signal(&cwc->cwc_cond);
+    tid = cwc->cwc_tid;
+    pthread_mutex_unlock(&cwc->cwc_mutex);
+    pthread_kill(tid, SIGTERM);
+    pthread_join(tid, NULL);
   }
 
-  if(!htsmsg_get_u32(values, "emm", &u32))
-    cwc->cwc_emm = u32;
-
-  if(!htsmsg_get_u32(values, "emmex", &u32))
-    cwc->cwc_emmex = u32;
-
-  cwc->cwc_reconfigure = 1;
-
-  if(cwc->cwc_fd != -1)
-    shutdown(cwc->cwc_fd, SHUT_RDWR);
-
-  pthread_cond_signal(&cwc->cwc_cond);
-
-  pthread_cond_broadcast(&cwc_config_changed);
-
-  return cwc_record_build(cwc);
 }
-  
-
 
 /**
  *
  */
 static int
-cwc_entry_delete(void *opaque, const char *id)
+caclient_cwc_class_deskey_set(void *o, const void *v)
 {
-  cwc_t *cwc;
-
-  pthread_cond_broadcast(&cwc_config_changed);
-
-  if((cwc = cwc_entry_find(id, 0)) == NULL)
-    return -1;
-  cwc_destroy(cwc);
-  return 0;
-}
-
-
-/**
- *
- */
-static htsmsg_t *
-cwc_entry_get_all(void *opaque)
-{
-  htsmsg_t *r = htsmsg_create_list();
-  cwc_t *cwc;
-
-  TAILQ_FOREACH(cwc, &cwcs, cwc_link)
-    htsmsg_add_msg(r, NULL, cwc_record_build(cwc));
-
-  return r;
+  cwc_t *cwc = o;
+  const char *s = v ?: "";
+  uint8_t key[14];
+  int i, u, l;
+
+  for(i = 0; i < ARRAY_SIZE(key); i++) {
+    while(*s != 0 && !isxdigit(*s)) s++;
+    u = *s ? nibble(*s++) : 0;
+    while(*s != 0 && !isxdigit(*s)) s++;
+    l = *s ? nibble(*s++) : 0;
+    key[i] = (u << 4) | l;
+  }
+  i = memcmp(cwc->cwc_confedkey, key, ARRAY_SIZE(key)) != 0;
+  memcpy(cwc->cwc_confedkey, key, ARRAY_SIZE(key));
+  return i;
 }
 
-/**
- *
- */
-static htsmsg_t *
-cwc_entry_get(void *opaque, const char *id)
+static const void *
+caclient_cwc_class_deskey_get(void *o)
 {
-  cwc_t *cwc;
-
-
-  if((cwc = cwc_entry_find(id, 0)) == NULL)
-    return NULL;
-  return cwc_record_build(cwc);
+  cwc_t *cwc = o;
+  static char buf[64];
+  static const char *ret = buf;
+  snprintf(buf, sizeof(buf),
+           "%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
+           "%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+           cwc->cwc_confedkey[0x0],
+           cwc->cwc_confedkey[0x1],
+           cwc->cwc_confedkey[0x2],
+           cwc->cwc_confedkey[0x3],
+           cwc->cwc_confedkey[0x4],
+           cwc->cwc_confedkey[0x5],
+           cwc->cwc_confedkey[0x6],
+           cwc->cwc_confedkey[0x7],
+           cwc->cwc_confedkey[0x8],
+           cwc->cwc_confedkey[0x9],
+           cwc->cwc_confedkey[0xa],
+           cwc->cwc_confedkey[0xb],
+           cwc->cwc_confedkey[0xc],
+           cwc->cwc_confedkey[0xd]);
+  return &ret;
 }
 
-/**
- *
- */
-/**
- *
- */
-static htsmsg_t *
-cwc_entry_create(void *opaque)
+const idclass_t caclient_cwc_class =
 {
-  pthread_cond_broadcast(&cwc_config_changed);
-  return cwc_record_build(cwc_entry_find(NULL, 1));
-}
-
-
-
-
-/**
- *
- */
-static const dtable_class_t cwc_dtc = {
-  .dtc_record_get     = cwc_entry_get,
-  .dtc_record_get_all = cwc_entry_get_all,
-  .dtc_record_create  = cwc_entry_create,
-  .dtc_record_update  = cwc_entry_update,
-  .dtc_record_delete  = cwc_entry_delete,
-  .dtc_read_access = ACCESS_ADMIN,
-  .dtc_write_access = ACCESS_ADMIN,
-  .dtc_mutex = &cwc_mutex,
+  .ic_super      = &caclient_class,
+  .ic_class      = "caclient_cwc",
+  .ic_caption    = "Code Word Client (newcamd)",
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_STR,
+      .id       = "username",
+      .name     = "Username",
+      .off      = offsetof(cwc_t, cwc_username),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "password",
+      .name     = "Password",
+      .off      = offsetof(cwc_t, cwc_password),
+      .opts     = PO_PASSWORD
+    },
+    {
+      .type     = PT_STR,
+      .id       = "hostname",
+      .name     = "Hostname / IP",
+      .opts     = PO_PASSWORD,
+      .off      = offsetof(cwc_t, cwc_hostname),
+      .def.s    = "localhost",
+    },
+    {
+      .type     = PT_INT,
+      .id       = "port",
+      .name     = "Port",
+      .off      = offsetof(cwc_t, cwc_port),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "deskey",
+      .name     = "DES Key",
+      .opts     = PO_PASSWORD,
+      .set      = caclient_cwc_class_deskey_set,
+      .get      = caclient_cwc_class_deskey_get,
+      .def.s    = "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d",
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "emm",
+      .name     = "Update Card (EMM)",
+      .off      = offsetof(cwc_t, cwc_emm),
+      .def.i    = 1
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "emmex",
+      .name     = "One Mux (EMM)",
+      .off      = offsetof(cwc_t, cwc_emmex),
+      .def.i    = 1
+    },
+    { }
+  }
 };
 
-
-
-/**
+/*
  *
  */
-void
-cwc_init(void)
+caclient_t *cwc_create(void)
 {
-  dtable_t *dt;
+  cwc_t *cwc = calloc(1, sizeof(*cwc));
 
-  TAILQ_INIT(&cwcs);
-  pthread_mutex_init(&cwc_mutex, NULL);
-  pthread_cond_init(&cwc_config_changed, NULL);
-
-  dt = dtable_create(&cwc_dtc, "cwc", NULL);
-  dtable_load(dt);
+  pthread_mutex_init(&cwc->cwc_mutex, NULL);
+  pthread_cond_init(&cwc->cwc_cond, NULL);
+  cwc->cac_free         = cwc_free;
+  cwc->cac_start        = cwc_service_start;
+  cwc->cac_conf_changed = cwc_conf_changed;
+  cwc->cac_caid_update  = cwc_caid_update;
+  return (caclient_t *)cwc;
 }
 
-
-/**
+/*
  *
  */
-void
-cwc_done(void)
-{
-  cwc_t *cwc;
-
-  dtable_delete("cwc");
-  pthread_mutex_lock(&global_lock);
-  pthread_mutex_lock(&cwc_mutex);
-  while ((cwc = TAILQ_FIRST(&cwcs)) != NULL)
-    cwc_destroy(cwc);
-  pthread_mutex_unlock(&cwc_mutex);
-  pthread_mutex_unlock(&global_lock);
-}
-
 
 #include <openssl/md5.h>
 
diff --git a/src/descrambler/cwc.h b/src/descrambler/cwc.h
deleted file mode 100644 (file)
index a256c1a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  tvheadend, CWC 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 CWC_H_
-#define CWC_H_
-
-struct mpegts_mux;
-
-void cwc_init(void);
-
-void cwc_done(void);
-
-void cwc_service_start(struct service *t);
-
-void cwc_caid_update(struct mpegts_mux *mux,
-                     uint16_t caid, uint16_t pid, int valid);
-
-#endif /* CWC_H_ */
index 17e72dc81dbe2594dca583a2fd846b72fe6ac304..3ab4a73eb71516c96d7ff4e67a86510e3e2443d5 100755 (executable)
@@ -18,8 +18,7 @@
 
 #include "tvheadend.h"
 #include "descrambler.h"
-#include "cwc.h"
-#include "capmt.h"
+#include "caclient.h"
 #include "ffdecsa/FFdecsa.h"
 #include "input.h"
 #include "tvhcsa.h"
@@ -81,26 +80,16 @@ static struct caid_tab caidnametab[] = {
 void
 descrambler_init ( void )
 {
-#if ENABLE_CWC
-  cwc_init();
-#endif
-#if ENABLE_CAPMT
-  capmt_init();
-#endif
 #if (ENABLE_CWC || ENABLE_CAPMT) && !ENABLE_DVBCSA
   ffdecsa_init();
 #endif
+  caclient_init();
 }
 
 void
 descrambler_done ( void )
 {
-#if ENABLE_CAPMT
-  capmt_done();
-#endif
-#if ENABLE_CWC
-  cwc_done();
-#endif
+  caclient_done();
 }
 
 /*
@@ -123,12 +112,7 @@ descrambler_service_start ( service_t *t )
     return;
 
   ((mpegts_service_t *)t)->s_dvb_mux->mm_descrambler_flush = 0;
-#if ENABLE_CWC
-  cwc_service_start(t);
-#endif
-#if ENABLE_CAPMT
-  capmt_service_start(t);
-#endif
+  caclient_start(t);
   if (t->s_descramble == NULL) {
     t->s_descramble = dr = calloc(1, sizeof(th_descrambler_runtime_t));
     sbuf_init(&dr->dr_buf);
@@ -638,9 +622,7 @@ descrambler_flush_tables( mpegts_mux_t *mux )
   if (mux == NULL)
     return;
   tvhtrace("descrambler", "mux %p - flush tables", mux);
-#if ENABLE_CWC
-  cwc_caid_update(mux, 0, 0, -1);
-#endif
+  caclient_caid_update(mux, 0, 0, -1);
   pthread_mutex_lock(&mux->mm_descrambler_lock);
   mux->mm_descrambler_flush = 1;
   while ((dt = TAILQ_FIRST(&mux->mm_descrambler_tables)) != NULL) {
@@ -690,9 +672,7 @@ descrambler_cat_data( mpegts_mux_t *mux, const uint8_t *data, int len )
       pid  = ((data[2] << 8) | data[3]) & 0x1fff;
       if (pid == 0)
         goto next;
-#if ENABLE_CWC
-      cwc_caid_update(mux, caid, pid, 1);
-#endif
+      caclient_caid_update(mux, caid, pid, 1);
       pthread_mutex_lock(&mux->mm_descrambler_lock);
       TAILQ_FOREACH(emm, &mux->mm_descrambler_emms, link)
         if (emm->caid == caid) {
@@ -724,11 +704,8 @@ next:
     }
   pthread_mutex_unlock(&mux->mm_descrambler_lock);
   while ((emm = TAILQ_FIRST(&removing)) != NULL) {
-    if (emm->pid != EMM_PID_UNKNOWN) {
-#if ENABLE_CWC
-      cwc_caid_update(mux, emm->caid, emm->pid, 0);
-#endif
-    }
+    if (emm->pid != EMM_PID_UNKNOWN)
+      caclient_caid_update(mux, emm->caid, emm->pid, 0);
     TAILQ_REMOVE(&removing, emm, link);
     free(emm);
   }
index d2979bed1578785a1be4e7f39495b7ba5f116244..9cafab08ce0b5f07f4a884a8bcb75333f19db4ec 100644 (file)
@@ -134,11 +134,8 @@ int              tvheadend_htsp_port_extra;
 const char      *tvheadend_cwd;
 const char      *tvheadend_webroot;
 const tvh_caps_t tvheadend_capabilities[] = {
-#if ENABLE_CWC
-  { "cwc", NULL },
-#endif
-#if ENABLE_CAPMT
-  { "capmt", NULL },
+#if ENABLE_CWC || ENABLE_CAPMT
+  { "caclient", NULL },
 #endif
 #if ENABLE_V4L
   { "v4l", NULL },
index 8af6d9d2349bdf50d12e3004c1f6f99fa33e8d18..ea709468bdc8b365929034e02b215d05de8166ff 100644 (file)
@@ -173,8 +173,10 @@ prop_write_values
         if (!(new = htsmsg_field_get_str(f)))
           continue;
         if (!p->set && strcmp((*str) ?: "", new)) {
-          free(*str);
+          /* make sure that the string is valid all time */
+          void *old = *str;
           *str = strdup(new);
+          free(old);
           save = 1;
         }
         break;
index 521102faf1201d1bce1d7aaad0dfb4b3eb86b7d7..8917e4608ce1c1dbfa7d40e81ab3a10b7003f3af 100755 (executable)
@@ -142,11 +142,8 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
   extjs_load(hq, "static/app/tableeditor.js");
   extjs_load(hq, "static/app/cteditor.js");
   extjs_load(hq, "static/app/acleditor.js");
-#if ENABLE_CWC
-  extjs_load(hq, "static/app/cwceditor.js");
-#endif
-#if ENABLE_CAPMT
-  extjs_load(hq, "static/app/capmteditor.js");
+#if ENABLE_CWC || ENABLE_CAPMT
+  extjs_load(hq, "static/app/caclient.js");
 #endif
   extjs_load(hq, "static/app/tvadapters.js");
   extjs_load(hq, "static/app/idnode.js");
diff --git a/src/webui/static/app/caclient.js b/src/webui/static/app/caclient.js
new file mode 100644 (file)
index 0000000..15224d3
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Conditional Access Client (cwc,capmt)
+ */
+
+tvheadend.caclient_builders = new Ext.data.JsonStore({
+  url: 'api/caclient/builders',
+  root: 'entries',
+  fields: ['class', 'caption', 'props'],
+  id: 'class',
+  autoLoad: true
+});
+
+tvheadend.caclient = function(panel, index) {
+    var list = 'enabled,name,username,password,hostname,mode,camdfilename,' +
+               'port,deskey,emm,emmex,comment';
+
+    tvheadend.idnode_form_grid(panel, {
+        url: 'api/caclient',
+        clazz: 'caclient',
+        comet: 'caclient',
+        titleS: 'CA',
+        titleP: 'CAs',
+        titleC: 'Client Name',
+        iconCls: 'key',
+        tabIndex: index,
+        list: { url: 'api/caclient/list', params: { } },
+        edit: { params: { list: list } },
+        add: {
+            url: 'api/caclient',
+            titleS: 'Conditional Access Client',
+            select: {
+                label: 'Type',
+                store: tvheadend.caclient_builders,
+                displayField: 'caption',
+                valueField: 'class',
+                propField: 'props',
+                list: list
+            },
+            create: { },
+        },
+        del: true,
+        move: true,
+        help: function() {
+            new tvheadend.help('Conditional Access Client', 'config_caclient.html');
+        },
+    });
+
+    return panel;
+
+}
index a40ce66bf2bde434f7f5d3bf94a44a16bff10e08..90fe413262439fc6527a198d6ea562e5393f148f 100644 (file)
@@ -107,6 +107,21 @@ tvheadend.idnode_enum_store = function(f)
     return store;
 };
 
+tvheadend.idnode_filter_fields = function(d, list)
+{
+  if (!list)
+    return d;
+  var o = list.split(',');
+  var r = [];
+  for (var i = 0; i < o.length; i++)
+    for (var j = 0; j < d.length; j++)
+      if (d[j].id === o[i]) {
+        r.push(d[j]);
+        break;
+      }
+  return r;
+}
+
 Ext.ux.grid.filter.IntsplitFilter = Ext.extend(Ext.ux.grid.filter.NumericFilter, {
 
     fieldCls : Ext.form.TextField,
@@ -838,6 +853,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
                 if (r) {
                     var d = r.get(conf.select.propField);
                     if (d) {
+                        d = tvheadend.idnode_filter_fields(d, conf.select.list || null);
                         pclass = r.get(conf.select.valueField);
                         win.setTitle('Add ' + s.lastSelectionText);
                         panel.remove(s);
@@ -980,11 +996,8 @@ tvheadend.idnode_grid = function(panel, conf)
         });
 
         /* Model */
-        var sortable = true;
-        if (conf.move)
-            sortable = false;
         var model = new Ext.grid.ColumnModel({
-            defaultSortable: sortable,
+            defaultSortable: conf.move ? false : true,
             columns: columns
         });
 
@@ -1441,10 +1454,12 @@ tvheadend.idnode_form_grid = function(panel, conf)
         var selectuuid = null;
 
         /* Store */
+        var listurl = conf.list ? conf.list.url : null;
+        var params = conf.list ? conf.list.params : null;
         store = new Ext.data.JsonStore({
             root: 'entries',
-            url: 'api/idnode/load',
-            baseParams: {
+            url: listurl || 'api/idnode/load',
+            baseParams: params || {
                 enum: 1,
                 'class': conf.clazz
             },
@@ -1454,10 +1469,7 @@ tvheadend.idnode_form_grid = function(panel, conf)
             fields: ['key','val'],
             remoteSort: false,
             pruneModifiedRecords: true,
-            sortInfo: {
-                field: 'val',
-                direction: 'ASC'
-            }
+            sortInfo: conf.move ? null : { field: 'val', direction: 'ASC' }
         });
 
         store.on('load', function(records) {
@@ -1476,12 +1488,12 @@ tvheadend.idnode_form_grid = function(panel, conf)
 
         /* Model */
         var model = new Ext.grid.ColumnModel({
-            defaultSortable: true,
+            defaultSortable: conf.move ? false : true,
             columns: [{
                 width: 300,
                 id: 'val',
                 header: conf.titleC,
-                sortable: true,
+                sortable: conf.move ? false : true,
                 dataIndex: 'val'
             }]
         });
@@ -1568,7 +1580,55 @@ tvheadend.idnode_form_grid = function(panel, conf)
             });
             buttons.push(abuttons.del);
         }
-        if (conf.add || conf.del)
+        if (conf.move) {
+            abuttons.up = new Ext.Toolbar.Button({
+                tooltip: 'Move selected entry up',
+                iconCls: 'moveup',
+                text: 'Move Up',
+                disabled: true,
+                handler: function() {
+                    var r = select.getSelections();
+                    if (r && r.length > 0) {
+                        var uuid = r[0].id;
+                        tvheadend.Ajax({
+                            url: 'api/idnode/moveup',
+                            params: {
+                                uuid: uuid
+                            },
+                            success: function(d)
+                            {
+                                store.reload();
+                            }
+                        });
+                    }
+                }
+            });
+            buttons.push(abuttons.up);
+            abuttons.down = new Ext.Toolbar.Button({
+                tooltip: 'Move selected entry down',
+                iconCls: 'movedown',
+                text: 'Move Down',
+                disabled: true,
+                handler: function() {
+                    var r = select.getSelections();
+                    if (r && r.length > 0) {
+                        var uuid = r[0].id;
+                        tvheadend.Ajax({
+                            url: 'api/idnode/movedown',
+                            params: {
+                                uuid: uuid
+                            },
+                            success: function(d)
+                            {
+                                store.reload();
+                            }
+                        });
+                    }
+                }
+            });
+            buttons.push(abuttons.down);
+        }
+        if (conf.add || conf.del || conf.move)
             buttons.push('-');
 
         /* Extra buttons */
@@ -1611,12 +1671,12 @@ tvheadend.idnode_form_grid = function(panel, conf)
                 return;
             if (current && current.uuid == r.id)
                 return;
+            var params = conf.edit ? (conf.edit.params || {}) : {};
+            params.uuid = r.id;
+            params.meta = 1;
             tvheadend.Ajax({
                 url: 'api/idnode/load',
-                params: {
-                    uuid: r.id,
-                    meta: 1
-                },
+                params: params,
                 success: function(d) {
                     d = json_decode(d);
                     roweditor_destroy();
@@ -1636,7 +1696,12 @@ tvheadend.idnode_form_grid = function(panel, conf)
                     }
                     abuttons.save.setDisabled(false);
                     abuttons.undo.setDisabled(false);
-                    abuttons.del.setDisabled(false);
+                    if (abuttons.del)
+                      abuttons.del.setDisabled(false);
+                    if (abuttons.up) {
+                      abuttons.up.setDisabled(false);
+                      abuttons.down.setDisabled(false);
+                    }
                     mpanel.add(editor);
                     mpanel.doLayout();
                 }
index e8b50519ec920baceda934dc78c67ef728c091a2..c96024b707ba696b871b57316a014097a7e6343e 100644 (file)
@@ -393,24 +393,8 @@ function accessUpdate(o) {
         cp.add(tsdvr);
 
         /* CSA */
-        if (tvheadend.capabilities.indexOf('cwc')   !== -1 ||
-            tvheadend.capabilities.indexOf('capmt') !== -1) {
-
-            var csa = new Ext.TabPanel({
-                activeTab: 0,
-                autoScroll: true,
-                title: 'CSA',
-                iconCls: 'key',
-                items: []
-            });
-
-            if (tvheadend.capabilities.indexOf('cwc')   !== -1)
-                tvheadend.cwceditor(csa);
-            if (tvheadend.capabilities.indexOf('capmt') !== -1)
-                tvheadend.capmteditor(csa);
-                
-            cp.add(csa);
-        }
+        if (tvheadend.capabilities.indexOf('caclient') !== -1)
+            tvheadend.caclient(cp, null);
 
         /* Stream Config */
         var stream = new Ext.TabPanel({