* Redesigned the DVB configuration tab in the web userinterface:

- Each adapter have three (or four) tabs
      o General setup and information
      o Grid of multiplexes
      o Grid of services
      o For sattelite adapters, a sattelite configuration tab.

  * Add support for disabling / enabling an entire DVB multiplex

  * Add support for multiple DiSEqC switchports on a single adapter

  * Add support for different sattelite LNBs

  * Graceful handling of DVB adapters that does not support many
    table filters in hardware. Tvheadend will rotate among the available
    ones.

  * Add support for enabling / disabling transports from the DVB configuration

  * Make it possible to remove DVB multiplexes from the web ui

  * Add 'Revert changes' button to all editable grids in the web ui

  * Make it possible to disable the idle scan on per-DVB adapter basis.
    The idle scan is a process to cycles through all multiplex to check
    the quality for each mux continously.
This commit is contained in:
Andreas Öman 2009-07-03 20:24:00 +00:00
parent 2fdcebb22b
commit 819f832a1c
36 changed files with 3278 additions and 1043 deletions

View file

@ -77,7 +77,8 @@ SRCS += src/dvb/dvb.c \
src/dvb/dvb_adapter.c \
src/dvb/dvb_multiplex.c \
src/dvb/dvb_transport.c \
src/dvb/dvb_preconf.c
src/dvb/dvb_preconf.c \
src/dvb/dvb_satconf.c \
#
# cwc

30
debian/changelog vendored
View file

@ -1,3 +1,33 @@
hts-tvheadend (2.3) hts; urgency=low
* Redesigned the DVB configuration tab in the web userinterface:
- Each adapter have three (or four) tabs
o General setup and information
o Grid of multiplexes
o Grid of services
o For sattelite adapters, a sattelite configuration tab.
* Add support for disabling / enabling an entire DVB multiplex
* Add support for multiple DiSEqC switchports on a single adapter
* Add support for different sattelite LNBs
* Graceful handling of DVB adapters that does not support many
table filters in hardware. Tvheadend will rotate among the available
ones.
* Add support for enabling / disabling transports from the DVB configuration
* Make it possible to remove DVB multiplexes from the web ui
* Add 'Revert changes' button to all editable grids in the web ui
* Make it possible to disable the idle scan on per-DVB adapter basis.
The idle scan is a process to cycles through all multiplex to check
the quality for each mux continously.
hts-tvheadend (2.2) hts; urgency=low
* Set $HOME so forked processes (XMLTV) will have correct environment

View file

@ -351,8 +351,6 @@ channel_rename(channel_t *ch, const char *newname)
channel_set_name(ch, newname);
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) {
free(t->tht_chname);
t->tht_chname = strdup(newname);
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t);
pthread_mutex_unlock(&t->tht_stream_mutex);
@ -387,12 +385,8 @@ channel_delete(channel_t *ch)
dvr_destroy_by_channel(ch);
while((t = LIST_FIRST(&ch->ch_transports)) != NULL) {
transport_unmap_channel(t);
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t);
pthread_mutex_unlock(&t->tht_stream_mutex);
}
while((t = LIST_FIRST(&ch->ch_transports)) != NULL)
transport_map_channel(t, NULL, 1);
while((s = LIST_FIRST(&ch->ch_subscriptions)) != NULL) {
LIST_REMOVE(s, ths_channel_link);
@ -434,13 +428,8 @@ channel_merge(channel_t *dst, channel_t *src)
tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" merged into \"%s\"",
src->ch_name, dst->ch_name);
while((t = LIST_FIRST(&src->ch_transports)) != NULL) {
transport_unmap_channel(t);
transport_map_channel(t, dst);
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t);
pthread_mutex_unlock(&t->tht_stream_mutex);
}
while((t = LIST_FIRST(&src->ch_transports)) != NULL)
transport_map_channel(t, dst, 1);
channel_delete(src);
}

View file

@ -37,9 +37,6 @@ void msleep(uint32_t msec)
;
}
#define printf(x...)
int diseqc_send_msg (int fd, fe_sec_voltage_t v, struct diseqc_cmd **cmd,
fe_sec_tone_mode_t t, fe_sec_mini_cmd_t b)
{

View file

@ -20,10 +20,32 @@
#define DVB_H_
#include <linux/dvb/frontend.h>
#include "htsmsg.h"
TAILQ_HEAD(th_dvb_adapter_queue, th_dvb_adapter);
RB_HEAD(th_dvb_mux_instance_tree, th_dvb_mux_instance);
TAILQ_HEAD(th_dvb_mux_instance_queue, th_dvb_mux_instance);
LIST_HEAD(th_dvb_mux_instance_list, th_dvb_mux_instance);
TAILQ_HEAD(dvb_satconf_queue, dvb_satconf);
/**
* Satconf
*/
typedef struct dvb_satconf {
char *sc_id;
TAILQ_ENTRY(dvb_satconf) sc_adapter_link;
int sc_port; // diseqc switchport (0 - 15)
char *sc_name;
char *sc_comment;
char *sc_lnb;
struct th_dvb_mux_instance_list sc_tdmis;
} dvb_satconf_t;
enum polarisation {
@ -42,8 +64,9 @@ enum polarisation {
typedef struct th_dvb_mux_instance {
RB_ENTRY(th_dvb_mux_instance) tdmi_global_link;
RB_ENTRY(th_dvb_mux_instance) tdmi_adapter_link;
LIST_ENTRY(th_dvb_mux_instance) tdmi_adapter_link;
LIST_ENTRY(th_dvb_mux_instance) tdmi_adapter_hash_link;
struct th_dvb_adapter *tdmi_adapter;
@ -55,8 +78,12 @@ typedef struct th_dvb_mux_instance {
int tdmi_fec_err_ptr;
time_t tdmi_time;
LIST_HEAD(, th_dvb_table) tdmi_tables;
TAILQ_HEAD(, th_dvb_table) tdmi_table_queue;
int64_t tdmi_table_start;
int tdmi_table_initial;
enum {
TDMI_FE_UNKNOWN,
@ -70,12 +97,13 @@ typedef struct th_dvb_mux_instance {
int tdmi_quality;
int tdmi_enabled;
time_t tdmi_got_adapter;
time_t tdmi_lost_adapter;
struct dvb_frontend_parameters tdmi_fe_params;
uint8_t tdmi_polarisation; /* for DVB-S */
uint8_t tdmi_switchport; /* for DVB-S */
uint16_t tdmi_transport_stream_id;
@ -88,29 +116,28 @@ typedef struct th_dvb_mux_instance {
TAILQ_ENTRY(th_dvb_mux_instance) tdmi_scan_link;
struct th_dvb_mux_instance_queue *tdmi_scan_queue;
LIST_ENTRY(th_dvb_mux_instance) tdmi_satconf_link;
dvb_satconf_t *tdmi_satconf;
} th_dvb_mux_instance_t;
#define DVB_MUX_SCAN_BAD 0 /* On the bad queue */
#define DVB_MUX_SCAN_OK 1 /* Ok, don't need to scan that often */
#define DVB_MUX_SCAN_INITIAL 2 /* To get a scan directly when a mux
is discovered */
/**
* DVB Adapter (one of these per physical adapter)
*/
#define TDA_MUX_HASH_WIDTH 101
typedef struct th_dvb_adapter {
TAILQ_ENTRY(th_dvb_adapter) tda_global_link;
struct th_dvb_mux_instance_tree tda_muxes;
struct th_dvb_mux_instance_list tda_muxes;
/**
* We keep our muxes on three queues in order to select how
* they are to be idle-scanned
*/
struct th_dvb_mux_instance_queue tda_scan_queues[3];
int tda_scan_selector; /* To alternate between bad and ok queue */
struct th_dvb_mux_instance_queue tda_scan_queues[2];
int tda_scan_selector;
struct th_dvb_mux_instance_queue tda_initial_scan_queue;
int tda_initial_num_mux;
th_dvb_mux_instance_t *tda_mux_current;
@ -119,6 +146,9 @@ typedef struct th_dvb_adapter {
const char *tda_rootpath;
char *tda_identifier;
uint32_t tda_autodiscovery;
uint32_t tda_idlescan;
uint32_t tda_logging;
char *tda_displayname;
int tda_fe_fd;
@ -137,6 +167,13 @@ typedef struct th_dvb_adapter {
gtimer_t tda_fe_monitor_timer;
int tda_fe_monitor_hold;
int tda_sat; // Set if this adapter is a satellite receiver (DVB-S, etc)
struct dvb_satconf_queue tda_satconfs;
struct th_dvb_mux_instance_list tda_mux_hash[TDA_MUX_HASH_WIDTH];
} th_dvb_adapter_t;
@ -153,18 +190,24 @@ void dvb_adapter_init(void);
void dvb_adapter_mux_scanner(void *aux);
void dvb_adapter_notify_reload(th_dvb_adapter_t *tda);
void dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s);
void dvb_adapter_set_auto_discovery(th_dvb_adapter_t *tda, int on);
void dvb_adapter_set_idlescan(th_dvb_adapter_t *tda, int on);
void dvb_adapter_set_logging(th_dvb_adapter_t *tda, int on);
void dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src);
void dvb_adapter_clean(th_dvb_adapter_t *tda);
int dvb_adapter_destroy(th_dvb_adapter_t *tda);
void dvb_adapter_notify(th_dvb_adapter_t *tda);
htsmsg_t *dvb_adapter_build_msg(th_dvb_adapter_t *tda);
/**
* DVB Multiplex
*/
@ -176,19 +219,38 @@ void dvb_mux_destroy(th_dvb_mux_instance_t *tdmi);
th_dvb_mux_instance_t *dvb_mux_create(th_dvb_adapter_t *tda,
struct dvb_frontend_parameters *fe_param,
int polarisation, int switchport,
int polarisation, dvb_satconf_t *sc,
uint16_t tsid, const char *network,
const char *logprefix);
const char *logprefix, int enabled,
const char *identifier);
void dvb_mux_set_networkname(th_dvb_mux_instance_t *tdmi, const char *name);
void dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid);
void dvb_mux_set_enable(th_dvb_mux_instance_t *tdmi, int enabled);
void dvb_mux_set_satconf(th_dvb_mux_instance_t *tdmi, const char *scid,
int save);
htsmsg_t *dvb_mux_build_msg(th_dvb_mux_instance_t *tdmi);
void dvb_mux_notify(th_dvb_mux_instance_t *tdmi);
/**
* DVB Transport (aka DVB service)
*/
void dvb_transport_load(th_dvb_mux_instance_t *tdmi);
th_transport_t *dvb_transport_find(th_dvb_mux_instance_t *tdmi,
uint16_t sid, int pmt_pid);
uint16_t sid, int pmt_pid,
const char *identifier);
void dvb_transport_notify(th_transport_t *t);
void dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda);
htsmsg_t *dvb_transport_build_msg(th_transport_t *t);
/**
@ -211,4 +273,19 @@ void dvb_table_add_transport(th_dvb_mux_instance_t *tdmi, th_transport_t *t,
void dvb_table_flush_all(th_dvb_mux_instance_t *tdmi);
/**
* Satellite configuration
*/
void dvb_satconf_init(th_dvb_adapter_t *tda);
htsmsg_t *dvb_satconf_list(th_dvb_adapter_t *tda);
htsmsg_t *dvb_lnblist_get(void);
dvb_satconf_t *dvb_satconf_entry_find(th_dvb_adapter_t *tda,
const char *id, int create);
void dvb_lnb_get_frequencies(const char *id,
int *f_low, int *f_hi, int *f_switch);
#endif /* DVB_H_ */

View file

@ -53,12 +53,13 @@ static void *dvb_adapter_input_dvr(void *aux);
static th_dvb_adapter_t *
tda_alloc(void)
{
int i;
th_dvb_adapter_t *tda = calloc(1, sizeof(th_dvb_adapter_t));
pthread_mutex_init(&tda->tda_delivery_mutex, NULL);
for(i = 0; i < 3; i++)
TAILQ_INIT(&tda->tda_scan_queues[i]);
TAILQ_INIT(&tda->tda_scan_queues[0]);
TAILQ_INIT(&tda->tda_scan_queues[1]);
TAILQ_INIT(&tda->tda_initial_scan_queue);
TAILQ_INIT(&tda->tda_satconfs);
return tda;
}
@ -76,6 +77,8 @@ tda_save(th_dvb_adapter_t *tda)
htsmsg_add_str(m, "type", dvb_adaptertype_to_str(tda->tda_type));
htsmsg_add_str(m, "displayname", tda->tda_displayname);
htsmsg_add_u32(m, "autodiscovery", tda->tda_autodiscovery);
htsmsg_add_u32(m, "idlescan", tda->tda_idlescan);
htsmsg_add_u32(m, "logging", tda->tda_logging);
hts_settings_save(m, "dvbadapters/%s", tda->tda_identifier);
htsmsg_destroy(m);
}
@ -87,8 +90,6 @@ tda_save(th_dvb_adapter_t *tda)
void
dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s)
{
htsmsg_t *m;
lock_assert(&global_lock);
if(!strcmp(s, tda->tda_displayname))
@ -97,17 +98,12 @@ dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s)
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" renamed to \"%s\"",
tda->tda_displayname, s);
m = htsmsg_create_map();
htsmsg_add_str(m, "id", tda->tda_identifier);
free(tda->tda_displayname);
tda->tda_displayname = strdup(s);
tda_save(tda);
htsmsg_add_str(m, "name", tda->tda_displayname);
notify_by_msg("dvbadapter", m);
dvb_adapter_notify(tda);
}
@ -122,7 +118,7 @@ dvb_adapter_set_auto_discovery(th_dvb_adapter_t *tda, int on)
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" mux autodiscovery set to %s",
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" mux autodiscovery set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_autodiscovery = on;
@ -130,6 +126,44 @@ dvb_adapter_set_auto_discovery(th_dvb_adapter_t *tda, int on)
}
/**
*
*/
void
dvb_adapter_set_idlescan(th_dvb_adapter_t *tda, int on)
{
if(tda->tda_idlescan == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" idle mux scanning set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_idlescan = on;
tda_save(tda);
}
/**
*
*/
void
dvb_adapter_set_logging(th_dvb_adapter_t *tda, int on)
{
if(tda->tda_logging == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" detailed logging set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_logging = on;
tda_save(tda);
}
/**
*
*/
@ -185,6 +219,9 @@ tda_add(const char *path)
tda->tda_identifier = strdup(buf);
tda->tda_autodiscovery = tda->tda_type != FE_QPSK;
tda->tda_idlescan = 1;
tda->tda_sat = tda->tda_type == FE_QPSK;
/* Come up with an initial displayname, user can change it and it will
be overridden by any stored settings later on */
@ -199,6 +236,11 @@ tda_add(const char *path)
pthread_create(&ptid, NULL, dvb_adapter_input_dvr, tda);
dvb_table_init(tda);
if(tda->tda_sat)
dvb_satconf_init(tda);
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 1);
}
@ -250,6 +292,8 @@ dvb_adapter_init(void)
tda->tda_displayname = strdup(name);
htsmsg_get_u32(c, "autodiscovery", &tda->tda_autodiscovery);
htsmsg_get_u32(c, "idlescan", &tda->tda_idlescan);
htsmsg_get_u32(c, "logging", &tda->tda_logging);
}
htsmsg_destroy(l);
}
@ -259,20 +303,6 @@ dvb_adapter_init(void)
}
/**
*
*/
void
dvb_adapter_notify_reload(th_dvb_adapter_t *tda)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", tda->tda_identifier);
htsmsg_add_u32(m, "reload", 1);
notify_by_msg("dvbadapter", m);
}
/**
* If nobody is subscribing, cycle thru all muxes to get some stats
* and EIT updates
@ -284,35 +314,38 @@ dvb_adapter_mux_scanner(void *aux)
th_dvb_mux_instance_t *tdmi;
int i;
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 10);
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 20);
if(LIST_FIRST(&tda->tda_muxes) == NULL)
return; // No muxes configured
if(transport_compute_weight(&tda->tda_transports) > 0)
return; /* someone is here */
/* Check if we have muxes pending for quickscan, if so, choose them */
tdmi = TAILQ_FIRST(&tda->tda_scan_queues[DVB_MUX_SCAN_INITIAL]);
/* If not, alternate between the other two (bad and OK) */
if(tdmi == NULL) {
for(i = 0; i < 2; i++) {
tda->tda_scan_selector = !tda->tda_scan_selector;
tdmi = TAILQ_FIRST(&tda->tda_scan_queues[tda->tda_scan_selector]);
if(tdmi != NULL) {
assert(tdmi->tdmi_scan_queue ==
&tda->tda_scan_queues[tda->tda_scan_selector]);
break;
}
}
} else {
assert(tdmi->tdmi_scan_queue ==
&tda->tda_scan_queues[DVB_MUX_SCAN_INITIAL]);
if((tdmi = TAILQ_FIRST(&tda->tda_initial_scan_queue)) != NULL) {
dvb_fe_tune(tdmi, "Initial autoscan");
return;
}
if(tdmi != NULL) {
/* Push to tail of queue */
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
dvb_fe_tune(tdmi, "Autoscan");
if(!tda->tda_idlescan && TAILQ_FIRST(&tda->tda_scan_queues[0]) == NULL) {
/* Idlescan is disabled and no muxes are bad.
If the currently tuned mux is ok, we can stick to it */
tdmi = tda->tda_mux_current;
if(tdmi != NULL && tdmi->tdmi_quality > 90)
return;
}
/* Alternate between the other two (bad and OK) */
for(i = 0; i < 2; i++) {
tda->tda_scan_selector = !tda->tda_scan_selector;
tdmi = TAILQ_FIRST(&tda->tda_scan_queues[tda->tda_scan_selector]);
if(tdmi != NULL) {
dvb_fe_tune(tdmi, "Autoscan");
return;
}
}
}
@ -328,18 +361,19 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
lock_assert(&global_lock);
while((tdmi_dst = RB_FIRST(&dst->tda_muxes)) != NULL)
while((tdmi_dst = LIST_FIRST(&dst->tda_muxes)) != NULL)
dvb_mux_destroy(tdmi_dst);
RB_FOREACH(tdmi_src, &src->tda_muxes, tdmi_adapter_link) {
LIST_FOREACH(tdmi_src, &src->tda_muxes, tdmi_adapter_link) {
tdmi_dst = dvb_mux_create(dst,
&tdmi_src->tdmi_fe_params,
tdmi_src->tdmi_polarisation,
tdmi_src->tdmi_switchport,
tdmi_src->tdmi_satconf,
tdmi_src->tdmi_transport_stream_id,
tdmi_src->tdmi_network,
"copy operation");
"copy operation", tdmi_src->tdmi_enabled,
NULL);
assert(tdmi_dst != NULL);
@ -347,10 +381,10 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
LIST_FOREACH(t_src, &tdmi_src->tdmi_transports, tht_mux_link) {
t_dst = dvb_transport_find(tdmi_dst,
t_src->tht_dvb_service_id,
t_src->tht_pmt_pid);
t_src->tht_pmt_pid, NULL);
t_dst->tht_pcr_pid = t_src->tht_pcr_pid;
t_dst->tht_disabled = t_src->tht_disabled;
t_dst->tht_enabled = t_src->tht_enabled;
t_dst->tht_servicetype = t_src->tht_servicetype;
t_dst->tht_scrambled = t_src->tht_scrambled;
@ -360,11 +394,8 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
if(t_src->tht_svcname != NULL)
t_dst->tht_svcname = strdup(t_src->tht_svcname);
if(t_src->tht_chname != NULL)
t_dst->tht_chname = strdup(t_src->tht_chname);
if(t_src->tht_ch != NULL)
transport_map_channel(t_dst, t_src->tht_ch);
transport_map_channel(t_dst, t_src->tht_ch, 0);
pthread_mutex_lock(&t_src->tht_stream_mutex);
@ -381,6 +412,7 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
st_dst->st_caid = st_src->st_caid;
}
t_dst->tht_config_change(t_dst); // Save config
pthread_mutex_unlock(&t_src->tht_stream_mutex);
}
@ -404,7 +436,7 @@ dvb_adapter_destroy(th_dvb_adapter_t *tda)
hts_settings_remove("dvbadapters/%s", tda->tda_identifier);
while((tdmi = RB_FIRST(&tda->tda_muxes)) != NULL)
while((tdmi = LIST_FIRST(&tda->tda_muxes)) != NULL)
dvb_mux_destroy(tdmi);
TAILQ_REMOVE(&dvb_adapters, tda, tda_global_link);
@ -464,3 +496,53 @@ dvb_adapter_input_dvr(void *aux)
pthread_mutex_unlock(&tda->tda_delivery_mutex);
}
}
/**
*
*/
htsmsg_t *
dvb_adapter_build_msg(th_dvb_adapter_t *tda)
{
char buf[100];
htsmsg_t *m = htsmsg_create_map();
th_dvb_mux_instance_t *tdmi;
th_transport_t *t;
int nummux = 0;
int numsvc = 0;
htsmsg_add_str(m, "identifier", tda->tda_identifier);
htsmsg_add_str(m, "name", tda->tda_displayname);
htsmsg_add_str(m, "path", tda->tda_rootpath);
htsmsg_add_str(m, "devicename", tda->tda_fe_info->name);
// XXX: bad bad bad slow slow slow
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
nummux++;
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
numsvc++;
}
}
htsmsg_add_u32(m, "services", numsvc);
htsmsg_add_u32(m, "muxes", nummux);
htsmsg_add_u32(m, "initialMuxes", tda->tda_initial_num_mux);
if(tda->tda_mux_current != NULL) {
dvb_mux_nicename(buf, sizeof(buf), tda->tda_mux_current);
htsmsg_add_str(m, "currentMux", buf);
}
htsmsg_add_u32(m, "satConf", tda->tda_sat);
return m;
}
/**
*
*/
void
dvb_adapter_notify(th_dvb_adapter_t *tda)
{
notify_by_msg("dvbAdapter", dvb_adapter_build_msg(tda));
}

View file

@ -39,33 +39,6 @@
#include "notify.h"
/**
*
*/
static void
dvb_notify_mux_quality(th_dvb_mux_instance_t *tdmi)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
notify_by_msg("dvbmux", m);
}
/**
*
*/
static void
dvb_notify_mux_status(th_dvb_mux_instance_t *tdmi)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_str(m, "status", dvb_mux_status(tdmi));
notify_by_msg("dvbmux", m);
}
/**
* Front end monitor
*
@ -76,14 +49,15 @@ dvb_fe_monitor(void *aux)
{
th_dvb_adapter_t *tda = aux;
fe_status_t fe_status;
int status, v, savemux = 0, vv, i, fec, q;
int status, v, update = 0, vv, i, fec, q;
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
char buf[50];
assert(tdmi != NULL);
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
if(tdmi == NULL)
return;
/**
* Read out front end status
*/
@ -140,13 +114,14 @@ dvb_fe_monitor(void *aux)
if(status != tdmi->tdmi_fe_status) {
tdmi->tdmi_fe_status = status;
dvb_notify_mux_status(tdmi);
dvb_mux_nicename(buf, sizeof(buf), tdmi);
DEBUGLOG("dvb", "\"%s\" on adapter \"%s\", status changed to %s",
buf, tda->tda_displayname, dvb_mux_status(tdmi));
savemux = 1;
if(tda->tda_logging) {
dvb_mux_nicename(buf, sizeof(buf), tdmi);
tvhlog(LOG_INFO,
"dvb", "\"%s\" on adapter \"%s\", status changed to %s",
buf, tda->tda_displayname, dvb_mux_status(tdmi));
}
update = 1;
}
if(status != TDMI_FE_UNKNOWN) {
@ -154,20 +129,22 @@ dvb_fe_monitor(void *aux)
q = MAX(MIN(q, 100), 0);
if(q != tdmi->tdmi_quality) {
tdmi->tdmi_quality = q;
dvb_notify_mux_quality(tdmi);
savemux = 1;
update = 1;
}
}
if(savemux)
if(update) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
notify_by_msg("dvbMux", m);
dvb_mux_save(tdmi);
}
}
/**
* Stop the given TDMI
*/
@ -175,19 +152,23 @@ void
dvb_fe_stop(th_dvb_mux_instance_t *tdmi)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
assert(tdmi == tda->tda_mux_current);
tda->tda_mux_current = NULL;
if(tdmi->tdmi_table_initial) {
tdmi->tdmi_table_initial = 0;
tda->tda_initial_num_mux--;
}
dvb_table_flush_all(tdmi);
if(tdmi->tdmi_scan_queue != NULL)
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
assert(tdmi->tdmi_scan_queue == NULL);
if(tdmi->tdmi_quality == 100) {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[DVB_MUX_SCAN_OK];
} else {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[DVB_MUX_SCAN_BAD];
if(tdmi->tdmi_enabled) {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[tdmi->tdmi_quality == 100];
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
}
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
time(&tdmi->tdmi_lost_adapter);
}
@ -204,12 +185,18 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
struct dvb_frontend_parameters p = tdmi->tdmi_fe_params;
char buf[256];
int r;
lock_assert(&global_lock);
if(tda->tda_mux_current == tdmi)
return;
if(tdmi->tdmi_scan_queue != NULL) {
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
tdmi->tdmi_scan_queue = NULL;
}
if(tda->tda_mux_current != NULL)
dvb_fe_stop(tda->tda_mux_current);
@ -217,20 +204,25 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
if(tda->tda_type == FE_QPSK) {
/* DVB-S */
int lowfreq, hifreq, switchfreq, hiband;
// lowfreq = atoi(config_get_str("lnb_lowfreq", "9750000" ));
// hifreq = atoi(config_get_str("lnb_hifreq", "10600000"));
// switchfreq = atoi(config_get_str("lnb_switchfreq", "11700000"));
dvb_satconf_t *sc;
int port, lowfreq, hifreq, switchfreq, hiband;
lowfreq = 9750000;
hifreq = 10600000;
switchfreq = 11700000;
port = 0;
if((sc = tdmi->tdmi_satconf) != NULL) {
port = sc->sc_port;
if(sc->sc_lnb != NULL)
dvb_lnb_get_frequencies(sc->sc_lnb, &lowfreq, &hifreq, &switchfreq);
}
hiband = switchfreq && p.frequency > switchfreq;
diseqc_setup(tda->tda_fe_fd,
0, /* switch position */
port,
tdmi->tdmi_polarisation == POLARISATION_HORIZONTAL,
hiband);
@ -246,8 +238,10 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
tda->tda_fe_monitor_hold = 4;
DEBUGLOG("dvb", "\"%s\" tuning to \"%s\" (%s)", tda->tda_rootpath, buf,
reason);
if(tda->tda_logging)
tvhlog(LOG_INFO,
"dvb", "\"%s\" tuning to \"%s\" (%s)", tda->tda_rootpath, buf,
reason);
r = ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, &p);
if(r != 0) {
@ -261,4 +255,6 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
dvb_table_add_default(tdmi);
dvb_adapter_notify(tda);
}

View file

@ -57,6 +57,24 @@ static struct strtab muxfestatustab[] = {
{ "OK", TDMI_FE_OK },
};
static void tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled);
/**
*
*/
static void
mux_link_initial(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi)
{
int was_empty = TAILQ_FIRST(&tda->tda_initial_scan_queue) == NULL;
tdmi->tdmi_scan_queue = &tda->tda_initial_scan_queue;
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
if(was_empty && (tda->tda_mux_current == NULL ||
tda->tda_mux_current->tdmi_table_initial == 0))
dvb_adapter_mux_scanner(tda);
}
/**
* Return a readable status text for the given mux
@ -68,23 +86,6 @@ dvb_mux_status(th_dvb_mux_instance_t *tdmi)
}
/**
*
*/
static int
tdmi_cmp(th_dvb_mux_instance_t *a, th_dvb_mux_instance_t *b)
{
if(a->tdmi_switchport != b->tdmi_switchport)
return a->tdmi_switchport - b->tdmi_switchport;
if(a->tdmi_fe_params.frequency != b->tdmi_fe_params.frequency)
return a->tdmi_fe_params.frequency - b->tdmi_fe_params.frequency;
return a->tdmi_polarisation - b->tdmi_polarisation;
}
/**
*
*/
@ -100,34 +101,63 @@ tdmi_global_cmp(th_dvb_mux_instance_t *a, th_dvb_mux_instance_t *b)
*/
th_dvb_mux_instance_t *
dvb_mux_create(th_dvb_adapter_t *tda, struct dvb_frontend_parameters *fe_param,
int polarisation, int switchport,
uint16_t tsid, const char *network, const char *source)
int polarisation, dvb_satconf_t *sc,
uint16_t tsid, const char *network, const char *source,
int enabled, const char *identifier)
{
th_dvb_mux_instance_t *tdmi;
static th_dvb_mux_instance_t *skel;
th_dvb_mux_instance_t *tdmi, *c;
unsigned int hash;
char buf[200];
char qpsktxt[20];
int entries_before = tda->tda_muxes.entries;
lock_assert(&global_lock);
if(skel == NULL)
skel = calloc(1, sizeof(th_dvb_mux_instance_t));
hash = (fe_param->frequency + polarisation) % TDA_MUX_HASH_WIDTH;
LIST_FOREACH(tdmi, &tda->tda_mux_hash[hash], tdmi_adapter_hash_link) {
if(tdmi->tdmi_fe_params.frequency == fe_param->frequency &&
tdmi->tdmi_polarisation == polarisation &&
tdmi->tdmi_satconf == sc)
/* Mux already exist */
return NULL;
}
skel->tdmi_polarisation = polarisation;
skel->tdmi_switchport = switchport;
skel->tdmi_fe_params.frequency = fe_param->frequency;
tdmi = calloc(1, sizeof(th_dvb_mux_instance_t));
tdmi = RB_INSERT_SORTED(&tda->tda_muxes, skel, tdmi_adapter_link, tdmi_cmp);
if(tdmi != NULL)
if(identifier == NULL) {
char qpsktxt[20];
if(tda->tda_sat)
snprintf(qpsktxt, sizeof(qpsktxt), "_%s",
dvb_polarisation_to_str(polarisation));
else
qpsktxt[0] = 0;
snprintf(buf, sizeof(buf), "%s%d%s%s%s",
tda->tda_identifier,fe_param->frequency, qpsktxt,
sc ? "_satconf_" : "", sc ? sc->sc_id : "");
tdmi->tdmi_identifier = strdup(buf);
} else {
tdmi->tdmi_identifier = strdup(identifier);
}
c = RB_INSERT_SORTED(&dvb_muxes, tdmi, tdmi_global_link, tdmi_global_cmp);
if(c != NULL) {
/* Global identifier collision, not good, not good at all */
tvhlog(LOG_ERR, "dvb",
"Multiple DVB multiplexes with same identifier \"%s\" "
"one is skipped", tdmi->tdmi_identifier);
free(tdmi->tdmi_identifier);
free(tdmi);
return NULL;
}
tdmi = skel;
skel = NULL;
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[DVB_MUX_SCAN_INITIAL];
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
tdmi->tdmi_enabled = enabled;
TAILQ_INIT(&tdmi->tdmi_table_queue);
tdmi->tdmi_transport_stream_id = tsid;
@ -135,36 +165,34 @@ dvb_mux_create(th_dvb_adapter_t *tda, struct dvb_frontend_parameters *fe_param,
tdmi->tdmi_network = network ? strdup(network) : NULL;
tdmi->tdmi_quality = 100;
if(entries_before == 0 && tda->tda_rootpath != NULL) {
/* First mux on adapter with backing hardware, start scanner */
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 1);
}
memcpy(&tdmi->tdmi_fe_params, fe_param,
sizeof(struct dvb_frontend_parameters));
if(tda->tda_type == FE_QPSK)
snprintf(qpsktxt, sizeof(qpsktxt), "_%s_%d",
dvb_polarisation_to_str(polarisation), switchport);
else
qpsktxt[0] = 0;
if(sc != NULL) {
tdmi->tdmi_satconf = sc;
LIST_INSERT_HEAD(&sc->sc_tdmis, tdmi, tdmi_satconf_link);
}
snprintf(buf, sizeof(buf), "%s%d%s",
tda->tda_identifier,fe_param->frequency, qpsktxt);
tdmi->tdmi_identifier = strdup(buf);
RB_INSERT_SORTED(&dvb_muxes, tdmi, tdmi_global_link, tdmi_global_cmp);
LIST_INSERT_HEAD(&tda->tda_mux_hash[hash], tdmi, tdmi_adapter_hash_link);
LIST_INSERT_HEAD(&tda->tda_muxes, tdmi, tdmi_adapter_link);
if(source != NULL) {
dvb_mux_nicename(buf, sizeof(buf), tdmi);
tvhlog(LOG_NOTICE, "dvb", "New mux \"%s\" created by %s", buf, source);
dvb_mux_save(tdmi);
dvb_adapter_notify_reload(tda);
dvb_adapter_notify(tda);
}
dvb_transport_load(tdmi);
dvb_mux_notify(tdmi);
if(enabled) {
tda->tda_initial_num_mux++;
tdmi->tdmi_table_initial = 1;
mux_link_initial(tda, tdmi);
}
return tdmi;
}
@ -181,25 +209,40 @@ dvb_mux_destroy(th_dvb_mux_instance_t *tdmi)
lock_assert(&global_lock);
hts_settings_remove("dvbmuxes/%s/%s",
tda->tda_identifier, tdmi->tdmi_identifier);
tda->tda_identifier, tdmi->tdmi_identifier);
while((t = LIST_FIRST(&tdmi->tdmi_transports)) != NULL)
while((t = LIST_FIRST(&tdmi->tdmi_transports)) != NULL) {
hts_settings_remove("dvbtransports/%s/%s",
t->tht_dvb_mux_instance->tdmi_identifier,
t->tht_identifier);
transport_destroy(t);
}
dvb_transport_notify_by_adapter(tda);
if(tda->tda_mux_current == tdmi)
dvb_fe_stop(tda->tda_mux_current);
if(tdmi->tdmi_satconf != NULL)
LIST_REMOVE(tdmi, tdmi_satconf_link);
RB_REMOVE(&dvb_muxes, tdmi, tdmi_global_link);
RB_REMOVE(&tda->tda_muxes, tdmi, tdmi_adapter_link);
LIST_REMOVE(tdmi, tdmi_adapter_link);
LIST_REMOVE(tdmi, tdmi_adapter_hash_link);
if(tdmi->tdmi_scan_queue != NULL)
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
if(tdmi->tdmi_table_initial)
tda->tda_initial_num_mux--;
hts_settings_remove("dvbmuxes/%s", tdmi->tdmi_identifier);
free(tdmi->tdmi_network);
free(tdmi->tdmi_identifier);
free(tdmi);
dvb_adapter_notify(tda);
}
@ -293,6 +336,7 @@ dvb_mux_save(th_dvb_mux_instance_t *tdmi)
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
htsmsg_add_u32(m, "enabled", tdmi->tdmi_enabled);
htsmsg_add_str(m, "status", dvb_mux_status(tdmi));
htsmsg_add_u32(m, "transportstreamid", tdmi->tdmi_transport_stream_id);
@ -333,9 +377,7 @@ dvb_mux_save(th_dvb_mux_instance_t *tdmi)
htsmsg_add_str(m, "polarisation",
val2str(tdmi->tdmi_polarisation, poltab));
htsmsg_add_u32(m, "switchport", tdmi->tdmi_switchport);
break;
break;
case FE_QAM:
htsmsg_add_u32(m, "symbol_rate", f->u.qam.symbol_rate);
@ -351,6 +393,9 @@ dvb_mux_save(th_dvb_mux_instance_t *tdmi)
break;
}
if(tdmi->tdmi_satconf != NULL)
htsmsg_add_str(m, "satconf", tdmi->tdmi_satconf->sc_id);
hts_settings_save(m, "dvbmuxes/%s/%s",
tdmi->tdmi_adapter->tda_identifier, tdmi->tdmi_identifier);
htsmsg_destroy(m);
@ -361,7 +406,7 @@ dvb_mux_save(th_dvb_mux_instance_t *tdmi)
*
*/
static const char *
tdmi_create_by_msg(th_dvb_adapter_t *tda, htsmsg_t *m)
tdmi_create_by_msg(th_dvb_adapter_t *tda, htsmsg_t *m, const char *identifier)
{
th_dvb_mux_instance_t *tdmi;
struct dvb_frontend_parameters f;
@ -369,7 +414,8 @@ tdmi_create_by_msg(th_dvb_adapter_t *tda, htsmsg_t *m)
int r;
int polarisation = 0;
unsigned int switchport = 0;
unsigned int tsid, u32;
unsigned int tsid, u32, enabled;
dvb_satconf_t *sc;
memset(&f, 0, sizeof(f));
@ -456,27 +502,24 @@ tdmi_create_by_msg(th_dvb_adapter_t *tda, htsmsg_t *m)
if(htsmsg_get_u32(m, "transportstreamid", &tsid))
tsid = 0xffff;
tdmi = dvb_mux_create(tda, &f, polarisation, switchport,
tsid, htsmsg_get_str(m, "network"), NULL);
if(htsmsg_get_u32(m, "enabled", &enabled))
enabled = 1;
if((s = htsmsg_get_str(m, "satconf")) != NULL)
sc = dvb_satconf_entry_find(tda, s, 0);
else
sc = NULL;
tdmi = dvb_mux_create(tda, &f, polarisation, sc,
tsid, htsmsg_get_str(m, "network"), NULL, enabled,
identifier);
if(tdmi != NULL) {
if((s = htsmsg_get_str(m, "status")) != NULL)
tdmi->tdmi_fe_status = str2val(s, muxfestatustab);
if(!htsmsg_get_u32(m, "quality", &u32)) {
if(!htsmsg_get_u32(m, "quality", &u32))
tdmi->tdmi_quality = u32;
if(tdmi->tdmi_scan_queue != NULL)
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
if(tdmi->tdmi_quality == 100) {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[DVB_MUX_SCAN_OK];
} else {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[DVB_MUX_SCAN_BAD];
}
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
}
}
return NULL;
}
@ -499,27 +542,122 @@ dvb_mux_load(th_dvb_adapter_t *tda)
if((c = htsmsg_get_map_by_field(f)) == NULL)
continue;
tdmi_create_by_msg(tda, c);
tdmi_create_by_msg(tda, c, f->hmf_name);
}
htsmsg_destroy(l);
}
/**
*
*/
void
dvb_mux_set_networkname(th_dvb_mux_instance_t *tdmi, const char *networkname)
{
htsmsg_t *m = htsmsg_create_map();
char buf[100];
htsmsg_t *m;
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
free((void *)tdmi->tdmi_network);
free(tdmi->tdmi_network);
tdmi->tdmi_network = strdup(networkname);
dvb_mux_save(tdmi);
dvb_mux_nicename(buf, sizeof(buf), tdmi);
htsmsg_add_str(m, "name", buf);
notify_by_msg("dvbmux", m);
m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
notify_by_msg("dvbMux", m);
}
/**
*
*/
void
dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid)
{
htsmsg_t *m;
tdmi->tdmi_transport_stream_id = tsid;
dvb_mux_save(tdmi);
m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_u32(m, "muxid", tdmi->tdmi_transport_stream_id);
notify_by_msg("dvbMux", m);
}
/**
*
*/
static void
tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
if(tdmi->tdmi_enabled == enabled)
return;
if(tdmi->tdmi_enabled) {
if(tdmi->tdmi_scan_queue != NULL) {
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
tdmi->tdmi_scan_queue = NULL;
}
}
tdmi->tdmi_enabled = enabled;
if(enabled)
mux_link_initial(tda, tdmi);
}
/**
* Configurable by user
*/
void
dvb_mux_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
{
tdmi_set_enable(tdmi, enabled);
dvb_mux_save(tdmi);
}
/**
*
*/
htsmsg_t *
dvb_mux_build_msg(th_dvb_mux_instance_t *tdmi)
{
htsmsg_t *m = htsmsg_create_map();
char buf[100];
m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_u32(m, "enabled", tdmi->tdmi_enabled);
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
dvb_mux_nicefreq(buf, sizeof(buf), tdmi);
htsmsg_add_str(m, "freq", buf);
htsmsg_add_str(m, "pol",
dvb_polarisation_to_str_long(tdmi->tdmi_polarisation));
if(tdmi->tdmi_satconf != NULL)
htsmsg_add_str(m, "satconf", tdmi->tdmi_satconf->sc_id);
if(tdmi->tdmi_transport_stream_id != 0xffff)
htsmsg_add_u32(m, "muxid", tdmi->tdmi_transport_stream_id);
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
return m;
}
/**
*
*/
void
dvb_mux_notify(th_dvb_mux_instance_t *tdmi)
{
notify_by_msg("dvbMux", dvb_mux_build_msg(tdmi));
}

View file

@ -40,17 +40,17 @@
*/
static void
dvb_mux_preconf_add(th_dvb_adapter_t *tda, const struct mux *m, int num,
const char *source)
const char *source, const char *satconf)
{
struct dvb_frontend_parameters f;
int i;
int polarisation;
int switchport;
int i, polarisation;
dvb_satconf_t *sc;
sc = dvb_satconf_entry_find(tda, satconf, 0);
for(i = 0; i < num; i++) {
polarisation = 0;
switchport = 0;
memset(&f, 0, sizeof(f));
@ -92,8 +92,7 @@ dvb_mux_preconf_add(th_dvb_adapter_t *tda, const struct mux *m, int num,
break;
}
dvb_mux_create(tda, &f, polarisation, switchport, 0xffff, NULL,
source);
dvb_mux_create(tda, &f, polarisation, sc, 0xffff, NULL, source, 1, NULL);
m++;
}
}
@ -103,7 +102,8 @@ dvb_mux_preconf_add(th_dvb_adapter_t *tda, const struct mux *m, int num,
*
*/
int
dvb_mux_preconf_add_network(th_dvb_adapter_t *tda, const char *id)
dvb_mux_preconf_add_network(th_dvb_adapter_t *tda, const char *id,
const char *satconf)
{
const struct region *r;
const struct network *n;
@ -135,7 +135,7 @@ dvb_mux_preconf_add_network(th_dvb_adapter_t *tda, const char *id)
for(j = 0; j < nn; j++) {
if(!strcmp(n[j].name, id)) {
dvb_mux_preconf_add(tda, n[j].muxes, n[j].nmuxes, source);
dvb_mux_preconf_add(tda, n[j].muxes, n[j].nmuxes, source, satconf);
break;
}
}

View file

@ -23,6 +23,7 @@
htsmsg_t *dvb_mux_preconf_get_node(int fetype, const char *node);
int dvb_mux_preconf_add_network(th_dvb_adapter_t *tda, const char *id);
int dvb_mux_preconf_add_network(th_dvb_adapter_t *tda, const char *id,
const char *satconf);
#endif /* DVB_MUXCONFIG_H */

331
src/dvb/dvb_satconf.c Normal file
View file

@ -0,0 +1,331 @@
/*
* Satconf
* Copyright (C) 2009 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include "tvhead.h"
#include "dvb.h"
#include "dtable.h"
#include "notify.h"
/**
*
*/
static void
satconf_notify(th_dvb_adapter_t *tda)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "adapterId", tda->tda_identifier);
notify_by_msg("dvbSatConf", m);
}
/**
*
*/
dvb_satconf_t *
dvb_satconf_entry_find(th_dvb_adapter_t *tda, const char *id, int create)
{
char buf[20];
dvb_satconf_t *sc;
static int tally;
if(id != NULL) {
TAILQ_FOREACH(sc, &tda->tda_satconfs, sc_adapter_link)
if(!strcmp(sc->sc_id, id))
return sc;
}
if(create == 0)
return NULL;
if(id == NULL) {
tally++;
snprintf(buf, sizeof(buf), "%d", tally);
id = buf;
} else {
tally = MAX(atoi(id), tally);
}
sc = calloc(1, sizeof(dvb_satconf_t));
sc->sc_id = strdup(id);
sc->sc_lnb = strdup("Universal");
TAILQ_INSERT_TAIL(&tda->tda_satconfs, sc, sc_adapter_link);
return sc;
}
/**
*
*/
static void
satconf_destroy(th_dvb_adapter_t *tda, dvb_satconf_t *sc)
{
th_dvb_mux_instance_t *tdmi;
while((tdmi = LIST_FIRST(&sc->sc_tdmis)) != NULL) {
tdmi->tdmi_satconf = NULL;
LIST_REMOVE(tdmi, tdmi_satconf_link);
}
TAILQ_REMOVE(&tda->tda_satconfs, sc, sc_adapter_link);
free(sc->sc_id);
free(sc->sc_name);
free(sc->sc_comment);
free(sc);
}
/**
*
*/
static htsmsg_t *
satconf_record_build(dvb_satconf_t *sc)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", sc->sc_id);
htsmsg_add_u32(m, "port", sc->sc_port);
htsmsg_add_str(m, "name", sc->sc_name ?: "");
htsmsg_add_str(m, "comment", sc->sc_comment ?: "");
htsmsg_add_str(m, "lnb", sc->sc_lnb);
return m;
}
/**
*
*/
static htsmsg_t *
satconf_entry_update(void *opaque, const char *id, htsmsg_t *values,
int maycreate)
{
th_dvb_adapter_t *tda = opaque;
uint32_t u32;
dvb_satconf_t *sc;
if((sc = dvb_satconf_entry_find(tda, id, maycreate)) == NULL)
return NULL;
lock_assert(&global_lock);
tvh_str_update(&sc->sc_name, htsmsg_get_str(values, "name"));
tvh_str_update(&sc->sc_comment, htsmsg_get_str(values, "comment"));
tvh_str_update(&sc->sc_lnb, htsmsg_get_str(values, "lnb"));
if(!htsmsg_get_u32(values, "port", &u32))
sc->sc_port = u32;
satconf_notify(tda);
return satconf_record_build(sc);
}
/**
*
*/
static int
satconf_entry_delete(void *opaque, const char *id)
{
th_dvb_adapter_t *tda = opaque;
dvb_satconf_t *sc;
if((sc = dvb_satconf_entry_find(tda, id, 0)) == NULL)
return -1;
satconf_destroy(tda, sc);
satconf_notify(tda);
return 0;
}
/**
*
*/
static htsmsg_t *
satconf_entry_get_all(void *opaque)
{
th_dvb_adapter_t *tda = opaque;
htsmsg_t *r = htsmsg_create_list();
dvb_satconf_t *sc;
TAILQ_FOREACH(sc, &tda->tda_satconfs, sc_adapter_link)
htsmsg_add_msg(r, NULL, satconf_record_build(sc));
return r;
}
/**
*
*/
static htsmsg_t *
satconf_entry_get(void *opaque, const char *id)
{
th_dvb_adapter_t *tda = opaque;
dvb_satconf_t *sc;
if((sc = dvb_satconf_entry_find(tda, id, 0)) == NULL)
return NULL;
return satconf_record_build(sc);
}
/**
*
*/
static htsmsg_t *
satconf_entry_create(void *opaque)
{
th_dvb_adapter_t *tda = opaque;
return satconf_record_build(dvb_satconf_entry_find(tda, NULL, 1));
}
/**
*
*/
static const dtable_class_t satconf_dtc = {
.dtc_record_get = satconf_entry_get,
.dtc_record_get_all = satconf_entry_get_all,
.dtc_record_create = satconf_entry_create,
.dtc_record_update = satconf_entry_update,
.dtc_record_delete = satconf_entry_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
};
/**
*
*/
void
dvb_satconf_init(th_dvb_adapter_t *tda)
{
dtable_t *dt;
char name[256];
dvb_satconf_t *sc;
htsmsg_t *r;
snprintf(name, sizeof(name), "dvbsatconf/%s", tda->tda_identifier);
dt = dtable_create(&satconf_dtc, name, tda);
if(!dtable_load(dt)) {
sc = dvb_satconf_entry_find(tda, NULL, 1);
sc->sc_comment = strdup("Default satconf entry");
sc->sc_name = strdup("Default (Port 0, Universal LNB)");
r = satconf_record_build(sc);
dtable_record_store(dt, sc->sc_id, r);
htsmsg_destroy(r);
}
}
/**
*
*/
htsmsg_t *
dvb_satconf_list(th_dvb_adapter_t *tda)
{
dvb_satconf_t *sc;
htsmsg_t *array = htsmsg_create_list();
htsmsg_t *m;
TAILQ_FOREACH(sc, &tda->tda_satconfs, sc_adapter_link) {
m = htsmsg_create_map();
htsmsg_add_str(m, "identifier", sc->sc_id);
htsmsg_add_str(m, "name", sc->sc_name);
htsmsg_add_msg(array, NULL, m);
}
return array;
}
/**
*
*/
static void
add_to_lnblist(htsmsg_t *array, const char *n)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "identifier", n);
htsmsg_add_msg(array, NULL, m);
}
/**
*
*/
htsmsg_t *
dvb_lnblist_get(void)
{
htsmsg_t *array = htsmsg_create_list();
add_to_lnblist(array, "Universal");
add_to_lnblist(array, "DBS");
add_to_lnblist(array, "Standard");
add_to_lnblist(array, "Enhanced");
add_to_lnblist(array, "C-Band");
add_to_lnblist(array, "C-Multi");
return array;
}
/**
*
*/
void
dvb_lnb_get_frequencies(const char *id, int *f_low, int *f_hi, int *f_switch)
{
if(!strcmp(id, "Universal")) {
*f_low = 9750000;
*f_hi = 10600000;
*f_switch = 11700000;
} else if(!strcmp(id, "DBS")) {
*f_low = 11250000;
*f_hi = 0;
*f_switch = 0;
} else if(!strcmp(id, "Standard")) {
*f_low = 10000000;
*f_hi = 0;
*f_switch = 0;
} else if(!strcmp(id, "Enhanced")) {
*f_low = 9750000;
*f_hi = 0;
*f_switch = 0;
} else if(!strcmp(id, "C-Band")) {
*f_low = 5150000;
*f_hi = 0;
*f_switch = 0;
} else if(!strcmp(id, "C-Multi")) {
*f_low = 5150000;
*f_hi = 5750000;
*f_switch = 0;
}
}

View file

@ -266,7 +266,7 @@ dvb_polarisation_to_str(int pol)
}
}
static const char *
const char *
dvb_polarisation_to_str_long(int pol)
{
switch(pol) {
@ -309,6 +309,25 @@ nicenum(char *x, size_t siz, unsigned int v)
return x;
}
/**
*
*/
void
dvb_mux_nicefreq(char *buf, size_t size, th_dvb_mux_instance_t *tdmi)
{
char freq[50];
if(tdmi->tdmi_adapter->tda_type == FE_QPSK) {
nicenum(freq, sizeof(freq), tdmi->tdmi_fe_params.frequency);
snprintf(buf, size, "%s kHz", freq);
} else {
nicenum(freq, sizeof(freq), tdmi->tdmi_fe_params.frequency / 1000);
snprintf(buf, size, "%s kHz", freq);
}
}
/**
*
*/
@ -320,10 +339,11 @@ dvb_mux_nicename(char *buf, size_t size, th_dvb_mux_instance_t *tdmi)
if(tdmi->tdmi_adapter->tda_type == FE_QPSK) {
nicenum(freq, sizeof(freq), tdmi->tdmi_fe_params.frequency);
snprintf(buf, size, "%s%s%s kHz %s port %d",
snprintf(buf, size, "%s%s%s kHz %s (%s)",
n?:"", n ? ": ":"", freq,
dvb_polarisation_to_str_long(tdmi->tdmi_polarisation),
tdmi->tdmi_switchport);
tdmi->tdmi_satconf ? tdmi->tdmi_satconf->sc_name : "No satconf");
} else {
nicenum(freq, sizeof(freq), tdmi->tdmi_fe_params.frequency / 1000);
snprintf(buf, size, "%s%s%s kHz", n?:"", n ? ": ":"", freq);

View file

@ -59,11 +59,13 @@ time_t dvb_convert_date(uint8_t *dvb_buf);
const char *dvb_adaptertype_to_str(int type);
int dvb_str_to_adaptertype(const char *str);
const char *dvb_polarisation_to_str(int pol);
const char *dvb_polarisation_to_str_long(int pol);
th_dvb_adapter_t *dvb_adapter_find_by_identifier(const char *identifier);
th_dvb_mux_instance_t *dvb_mux_find_by_identifier(const char *identifier);
void dvb_mux_nicename(char *buf, size_t size, th_dvb_mux_instance_t *tdmi);
int dvb_mux_badness(th_dvb_mux_instance_t *tdmi);
const char *dvb_mux_status(th_dvb_mux_instance_t *tdmi);
void dvb_conversion_init(void);
void dvb_mux_nicefreq(char *buf, size_t size, th_dvb_mux_instance_t *tdmi);
#endif /* DVB_SUPPORT_H */

View file

@ -76,8 +76,8 @@ typedef struct th_dvb_table {
char *tdt_name;
void *tdt_opaque;
void (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
uint8_t tableid, void *opaque);
int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
uint8_t tableid, void *opaque);
int tdt_count;
@ -102,27 +102,33 @@ dvb_fparams_alloc(void)
}
/**
*
*/
static void
dvb_table_fastswitch(th_dvb_mux_instance_t *tdmi)
{
#if 0
th_dvb_table_t *tdt;
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
char buf[100];
if(tdmi->tdmi_quickscan == TDMI_QUICKSCAN_NONE)
if(!tdmi->tdmi_table_initial)
return;
LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link)
if(tdt->tdt_quickreq && tdt->tdt_count == 0)
if((tdt->tdt_flags & TDT_QUICKREQ) && tdt->tdt_count == 0)
return;
tdmi->tdmi_quickscan = TDMI_QUICKSCAN_NONE;
dvb_adapter_mux_scanner(tdmi->tdmi_adapter);
#endif
tdmi->tdmi_table_initial = 0;
tda->tda_initial_num_mux--;
if(tda->tda_logging) {
dvb_mux_nicename(buf, sizeof(buf), tdmi);
tvhlog(LOG_INFO, "dvb", "\"%s\" initial scan completed for \"%s\"",
tda->tda_rootpath, buf);
}
dvb_adapter_mux_scanner(tda);
}
@ -188,9 +194,10 @@ static void
dvb_proc_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt, uint8_t *sec,
int r)
{
int chkcrc = tdt->tdt_flags & TDT_INC_TABLE_HDR;
int chkcrc = tdt->tdt_flags & TDT_CRC;
int tableid, len;
uint8_t *ptr;
int ret;
/* It seems some hardware (or is it the dvb API?) does not
honour the DMX_CHECK_CRC flag, so we check it again */
@ -208,11 +215,15 @@ dvb_proc_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt, uint8_t *sec,
if(chkcrc) len -= 4; /* Strip trailing CRC */
if(tdt->tdt_flags & TDT_INC_TABLE_HDR)
tdt->tdt_callback(tdmi, sec, len + 3, tableid, tdt->tdt_opaque);
ret = tdt->tdt_callback(tdmi, sec, len + 3, tableid, tdt->tdt_opaque);
else
tdt->tdt_callback(tdmi, ptr, len, tableid, tdt->tdt_opaque);
ret = tdt->tdt_callback(tdmi, ptr, len, tableid, tdt->tdt_opaque);
dvb_table_fastswitch(tdmi);
if(ret == 0)
tdt->tdt_count++;
if(tdt->tdt_flags & TDT_QUICKREQ)
dvb_table_fastswitch(tdmi);
}
/**
@ -227,6 +238,7 @@ dvb_table_input(void *aux)
uint8_t sec[4096];
th_dvb_mux_instance_t *tdmi;
th_dvb_table_t *tdt;
int64_t t;
while(1) {
x = epoll_wait(tda->tda_table_epollfd, ev, sizeof(ev) / sizeof(ev[0]), -1);
@ -243,26 +255,36 @@ dvb_table_input(void *aux)
continue;
pthread_mutex_lock(&global_lock);
tdmi = tda->tda_mux_current;
if((tdmi = tda->tda_mux_current) != NULL) {
t = getclock_hires();
/*
* Supress first 250ms of table info. It seems that sometimes
* the tuners not have actually tuned once they have returned
* from the ioctl(). So we will wait some time before we start
* accepting tables.
* Not a perfect tix...
*/
if(t - tdmi->tdmi_table_start >= 250000) {
LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link)
if(tdt->tdt_id == tid)
break;
LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link)
if(tdt->tdt_id == tid)
break;
if(tdt != NULL) {
dvb_proc_table(tdmi, tdt, sec, r);
if(tdt != NULL) {
dvb_proc_table(tdmi, tdt, sec, r);
/* Any tables pending (that wants a filter/fd) */
if(TAILQ_FIRST(&tdmi->tdmi_table_queue) != NULL) {
tdt_close_fd(tdmi, tdt);
/* Any tables pending (that wants a filter/fd) */
if(TAILQ_FIRST(&tdmi->tdmi_table_queue) != NULL) {
tdt_close_fd(tdmi, tdt);
tdt = TAILQ_FIRST(&tdmi->tdmi_table_queue);
assert(tdt != NULL);
tdt = TAILQ_FIRST(&tdmi->tdmi_table_queue);
assert(tdt != NULL);
tdt_open_fd(tdmi, tdt);
tdt_open_fd(tdmi, tdt);
}
}
}
}
pthread_mutex_unlock(&global_lock);
}
}
@ -312,7 +334,7 @@ dvb_tdt_destroy(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi,
*/
static void
tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams,
void (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
uint8_t tableid, void *opaque), void *opaque,
const char *name, int flags, int pid, th_dvb_table_t *tdt)
{
@ -408,7 +430,7 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep,
/**
* DVB EIT (Event Information Table)
*/
static void
static int
dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
@ -444,7 +466,7 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
// printf("EIT!, tid = %x\n", tableid);
if(tableid < 0x4e || tableid > 0x6f || len < 11)
return;
return -1;
serviceid = ptr[0] << 8 | ptr[1];
version = ptr[2] >> 1 & 0x1f;
@ -460,20 +482,20 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
ptr += 11;
/* Search all muxes on adapter */
RB_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link)
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link)
if(tdmi->tdmi_transport_stream_id == transport_stream_id)
break;
if(tdmi == NULL)
return;
return -1;
t = dvb_transport_find(tdmi, serviceid, 0);
t = dvb_transport_find(tdmi, serviceid, 0, NULL);
if(t == NULL)
return;
return 0;
ch = t->tht_ch;
if(ch == NULL)
return;
return 0;
while(len >= 12) {
event_id = ptr[0] << 8 | ptr[1];
@ -537,13 +559,14 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
len -= dlen; ptr += dlen; dllen -= dlen;
}
}
return 0;
}
/**
* DVB SDT (Service Description Table)
*/
static void
static int
dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
@ -567,7 +590,7 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
int l;
if(len < 8)
return;
return -1;
transport_stream_id = ptr[0] << 8 | ptr[1];
version = ptr[2] >> 1 & 0x1f;
@ -629,7 +652,7 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
snprintf(chname0, sizeof(chname0), "noname-sid-0x%x", service_id);
}
t = dvb_transport_find(tdmi, service_id, 0);
t = dvb_transport_find(tdmi, service_id, 0, NULL);
if(t == NULL)
break;
@ -651,10 +674,6 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
t->tht_config_change(t);
pthread_mutex_unlock(&t->tht_stream_mutex);
}
if(t->tht_chname == NULL)
t->tht_chname = strdup(chname);
}
break;
}
@ -662,28 +681,27 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
len -= dlen; ptr += dlen; dllen -= dlen;
}
}
return 0;
}
/**
* PAT - Program Allocation table
*/
static void
static int
dvb_pat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
uint16_t service, pmt, tid;
uint16_t service, pmt, tsid;
th_transport_t *t;
if(len < 5)
return;
return -1;
tid = (ptr[0] << 8) | ptr[1];
tsid = (ptr[0] << 8) | ptr[1];
if(tdmi->tdmi_transport_stream_id != tid) {
tdmi->tdmi_transport_stream_id = tid;
dvb_mux_save(tdmi);
}
if(tdmi->tdmi_transport_stream_id != tsid)
dvb_mux_set_tsid(tdmi, tsid);
ptr += 5;
len -= 5;
@ -693,12 +711,13 @@ dvb_pat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
pmt = (ptr[2] & 0x1f) << 8 | ptr[3];
if(service != 0) {
t = dvb_transport_find(tdmi, service, pmt);
t = dvb_transport_find(tdmi, service, pmt, NULL);
dvb_table_add_transport(tdmi, t, pmt);
}
ptr += 4;
len -= 4;
}
return 0;
}
@ -712,16 +731,17 @@ typedef struct ca_stream {
/**
* CA - Conditional Access
*/
static void
static int
dvb_ca_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
return 0;
}
/**
* CAT - Conditional Access Table
*/
static void
static int
dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
@ -758,6 +778,7 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
ptr += tlen;
len -= tlen;
}
return 0;
}
@ -777,7 +798,7 @@ static const fe_modulation_t qam_tab [6] = {
/**
* Cable delivery descriptor
*/
static void
static int
dvb_table_cable_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint16_t tsid)
{
@ -785,11 +806,11 @@ dvb_table_cable_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
struct dvb_frontend_parameters fe_param;
if(!tdmi->tdmi_adapter->tda_autodiscovery)
return;
return -1;
if(len < 11) {
printf("Invalid CABLE DESCRIPTOR\n");
return;
return -1;
}
memset(&fe_param, 0, sizeof(fe_param));
fe_param.inversion = INVERSION_AUTO;
@ -815,13 +836,14 @@ dvb_table_cable_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
fe_param.u.qam.fec_inner = fec_tab[ptr[10] & 0x07];
dvb_mux_create(tdmi->tdmi_adapter, &fe_param, 0, 0, tsid, NULL,
"automatic mux discovery");
"automatic mux discovery", 1, NULL);
return 0;
}
/**
* Satellite delivery descriptor
*/
static void
static int
dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint16_t tsid)
{
@ -829,10 +851,10 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
struct dvb_frontend_parameters fe_param;
if(!tdmi->tdmi_adapter->tda_autodiscovery)
return;
return -1;
if(len < 11)
return;
return -1;
memset(&fe_param, 0, sizeof(fe_param));
fe_param.inversion = INVERSION_AUTO;
@ -851,9 +873,10 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
pol = (ptr[6] >> 5) & 0x03;
dvb_mux_create(tdmi->tdmi_adapter, &fe_param, pol, tdmi->tdmi_switchport,
dvb_mux_create(tdmi->tdmi_adapter, &fe_param, pol, tdmi->tdmi_satconf,
tsid, NULL,
"automatic mux discovery");
"automatic mux discovery", 1, NULL);
return 0;
}
@ -861,7 +884,7 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
/**
* NIT - Network Information Table
*/
static void
static int
dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
@ -874,13 +897,13 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
len -= 5;
if(tableid != 0x40)
return;
return -1;
ntl = ((ptr[0] & 0xf) << 8) | ptr[1];
ptr += 2;
len -= 2;
if(ntl > len)
return;
return -1;
while(ntl > 2) {
tag = *ptr++;
@ -891,7 +914,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
switch(tag) {
case DVB_DESC_NETWORK_NAME:
if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen))
return;
return -1;
if(strcmp(tdmi->tdmi_network ?: "", networkname))
dvb_mux_set_networkname(tdmi, networkname);
@ -904,14 +927,14 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
}
if(len < 2)
return;
return -1;
ntl = ((ptr[0] & 0xf) << 8) | ptr[1];
ptr += 2;
len -= 2;
if(len < ntl)
return;
return -1;
while(len >= 6) {
tsid = ( ptr[0] << 8) | ptr[1];
@ -942,6 +965,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
ntl -= tlen;
}
}
return 0;
}
@ -949,7 +973,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
/**
* PMT - Program Mapping Table
*/
static void
static int
dvb_pmt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
@ -958,23 +982,7 @@ dvb_pmt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
pthread_mutex_lock(&t->tht_stream_mutex);
psi_parse_pmt(t, ptr, len, 1);
pthread_mutex_unlock(&t->tht_stream_mutex);
}
/**
* RST - Running Status Table
*/
static void
dvb_rst_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
int i;
// printf("Got RST on %s\t", tdmi->tdmi_uniquename);
for(i = 0; i < len; i++)
printf("%02x.", ptr[i]);
printf("\n");
return 0;
}
@ -986,6 +994,8 @@ dvb_table_add_default(th_dvb_mux_instance_t *tdmi)
{
struct dmx_sct_filter_params *fp;
tdmi->tdmi_table_start = getclock_hires();
/* Program Allocation Table */
fp = dvb_fparams_alloc();
@ -1023,15 +1033,6 @@ dvb_table_add_default(th_dvb_mux_instance_t *tdmi)
fp = dvb_fparams_alloc();
tdt_add(tdmi, fp, dvb_eit_callback, NULL, "eit",
TDT_CRC, 0x12, NULL);
/* Running Status Table */
fp = dvb_fparams_alloc();
fp->filter.filter[0] = 0x71;
fp->filter.mask[0] = 0xff;
tdt_add(tdmi, fp, dvb_rst_callback, NULL, "rst",
TDT_CRC, 0x13, NULL);
}

View file

@ -68,6 +68,9 @@ dvb_transport_start(th_transport_t *t, unsigned int weight, int status,
if(tda->tda_rootpath == NULL)
return 1; /* hardware not present */
if(!tdmi->tdmi_enabled)
return 1; /* Mux is disabled */
/* Check if adapter is idle, or already tuned */
if(tdmi != NULL && tdmi != t->tht_dvb_mux_instance && !force_start) {
@ -178,7 +181,7 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi)
if(htsmsg_get_u32(c, "pmt", &pmt))
continue;
t = dvb_transport_find(tdmi, sid, pmt);
t = dvb_transport_find(tdmi, sid, pmt, f->hmf_name);
htsmsg_get_u32(c, "stype", &t->tht_servicetype);
if(htsmsg_get_u32(c, "scrambled", &u32))
@ -191,19 +194,16 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi)
s = htsmsg_get_str(c, "servicename") ?: "unknown";
t->tht_svcname = strdup(s);
s = htsmsg_get_str(c, "channelname");
if(s != NULL) {
t->tht_chname = strdup(s);
} else {
t->tht_chname = strdup(t->tht_svcname);
}
pthread_mutex_lock(&t->tht_stream_mutex);
psi_load_transport_settings(c, t);
pthread_mutex_unlock(&t->tht_stream_mutex);
if(!htsmsg_get_u32(c, "mapped", &u32) && u32)
transport_map_channel(t, NULL);
s = htsmsg_get_str(c, "channelname");
if(htsmsg_get_u32(c, "mapped", &u32))
u32 = 0;
if(s && u32)
transport_map_channel(t, channel_find_by_name(s, 1), 0);
}
htsmsg_destroy(l);
}
@ -229,10 +229,10 @@ dvb_transport_save(th_transport_t *t)
if(t->tht_svcname != NULL)
htsmsg_add_str(m, "servicename", t->tht_svcname);
if(t->tht_chname != NULL)
htsmsg_add_str(m, "channelname", t->tht_chname);
htsmsg_add_u32(m, "mapped", !!t->tht_ch);
if(t->tht_ch != NULL) {
htsmsg_add_str(m, "channelname", t->tht_ch->ch_name);
htsmsg_add_u32(m, "mapped", 1);
}
psi_save_transport_settings(m, t);
@ -241,6 +241,7 @@ dvb_transport_save(th_transport_t *t)
t->tht_identifier);
htsmsg_destroy(m);
dvb_transport_notify(t);
}
@ -257,7 +258,7 @@ dvb_transport_quality(th_transport_t *t)
lock_assert(&global_lock);
return tdmi->tdmi_quality;
return tdmi->tdmi_enabled ? tdmi->tdmi_quality : 0;
}
@ -297,10 +298,12 @@ dvb_transport_networkname(th_transport_t *t)
* If it cannot be found we create it if 'pmt_pid' is also set
*/
th_transport_t *
dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid)
dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
const char *identifier)
{
th_transport_t *t;
char tmp[200];
char buf[200];
lock_assert(&global_lock);
@ -312,9 +315,16 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid)
if(pmt_pid == 0)
return NULL;
snprintf(tmp, sizeof(tmp), "%s_%04x", tdmi->tdmi_identifier, sid);
if(identifier == NULL) {
snprintf(tmp, sizeof(tmp), "%s_%04x", tdmi->tdmi_identifier, sid);
identifier = tmp;
}
t = transport_create(tmp, TRANSPORT_DVB, THT_MPEG_TS);
dvb_mux_nicename(buf, sizeof(buf), tdmi);
if(tdmi->tdmi_adapter->tda_logging)
tvhlog(LOG_INFO, "dvb", "Add service \"%s\" on \"%s\"", identifier, buf);
t = transport_create(identifier, TRANSPORT_DVB, THT_MPEG_TS);
t->tht_dvb_service_id = sid;
t->tht_pmt_pid = pmt_pid;
@ -328,5 +338,67 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid)
t->tht_quality_index = dvb_transport_quality;
LIST_INSERT_HEAD(&tdmi->tdmi_transports, t, tht_mux_link);
dvb_adapter_notify(tdmi->tdmi_adapter);
return t;
}
/**
*
*/
htsmsg_t *
dvb_transport_build_msg(th_transport_t *t)
{
th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance;
htsmsg_t *m = htsmsg_create_map();
char buf[100];
htsmsg_add_str(m, "id", t->tht_identifier);
htsmsg_add_u32(m, "enabled", t->tht_enabled);
htsmsg_add_u32(m, "sid", t->tht_dvb_service_id);
htsmsg_add_u32(m, "pmt", t->tht_pmt_pid);
htsmsg_add_u32(m, "pcr", t->tht_pcr_pid);
htsmsg_add_str(m, "type", transport_servicetype_txt(t));
htsmsg_add_str(m, "svcname", t->tht_svcname ?: "");
htsmsg_add_str(m, "provider", t->tht_provider ?: "");
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
dvb_mux_nicefreq(buf, sizeof(buf), tdmi);
htsmsg_add_str(m, "mux", buf);
if(t->tht_ch != NULL)
htsmsg_add_str(m, "channelname", t->tht_ch->ch_name);
return m;
}
/**
*
*/
void
dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "adapterId", tda->tda_identifier);
notify_by_msg("dvbService", m);
}
/**
*
*/
void
dvb_transport_notify(th_transport_t *t)
{
th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance;
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "adapterId", tdmi->tdmi_adapter->tda_identifier);
notify_by_msg("dvbService", m);
}

View file

@ -211,6 +211,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
lock_assert(&t->tht_stream_mutex);
sid = ptr[0] << 8 | ptr[1];
pcr_pid = (ptr[5] & 0x1f) << 8 | ptr[6];
dllen = (ptr[7] & 0xf) << 8 | ptr[8];
@ -609,7 +610,7 @@ psi_save_transport_settings(htsmsg_t *m, th_transport_t *t)
htsmsg_add_u32(m, "pcr", t->tht_pcr_pid);
htsmsg_add_u32(m, "disabled", !!t->tht_disabled);
htsmsg_add_u32(m, "disabled", !t->tht_enabled);
lock_assert(&t->tht_stream_mutex);
@ -651,7 +652,9 @@ psi_load_transport_settings(htsmsg_t *m, th_transport_t *t)
t->tht_pcr_pid = u32;
if(!htsmsg_get_u32(m, "disabled", &u32))
t->tht_disabled = u32;
t->tht_enabled = !u32;
else
t->tht_enabled = 1;
HTSMSG_FOREACH(f, m) {
if(strcmp(f->hmf_name, "stream"))

View file

@ -141,7 +141,7 @@ rawts_transport_add(rawts_t *rt, uint16_t sid, int pmt_pid)
ch = channel_find_by_name(tmp, 1);
transport_map_channel(t, ch);
transport_map_channel(t, ch, 0);
return t;
}

View file

@ -109,6 +109,9 @@ serviceprobe_thread(void *aux)
was_doing_work = 1;
}
tvhlog(LOG_INFO, "serviceprobe", "%20s: checking...",
t->tht_svcname);
s = subscription_create_from_transport(t, "serviceprobe", &sq.sq_st);
transport_ref(t);
@ -154,13 +157,9 @@ serviceprobe_thread(void *aux)
t->tht_svcname, err);
} else if(t->tht_ch == NULL) {
ch = channel_find_by_name(t->tht_svcname, 1);
transport_map_channel(t, ch);
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t);
pthread_mutex_unlock(&t->tht_stream_mutex);
transport_map_channel(t, ch, 1);
tvhlog(LOG_INFO, "serviceprobe", "\"%s\" mapped to channel \"%s\"",
tvhlog(LOG_INFO, "serviceprobe", "%20s: mapped to channel \"%s\"",
t->tht_svcname, t->tht_svcname);
}

View file

@ -298,13 +298,13 @@ transport_find(channel_t *ch, unsigned int weight)
/* First, sort all transports in order */
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
if(!t->tht_disabled && t->tht_quality_index(t) > 10)
if(t->tht_enabled && t->tht_quality_index(t) > 10)
cnt++;
vec = alloca(cnt * sizeof(th_transport_t *));
i = 0;
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
if(!t->tht_disabled && t->tht_quality_index(t) > 10)
if(t->tht_enabled && t->tht_quality_index(t) > 10)
vec[i++] = t;
assert(i == cnt);
@ -415,7 +415,6 @@ transport_destroy(th_transport_t *t)
free(t->tht_identifier);
free(t->tht_svcname);
free(t->tht_chname);
free(t->tht_provider);
while((st = LIST_FIRST(&t->tht_components)) != NULL) {
@ -443,6 +442,7 @@ transport_create(const char *identifier, int type, int source_type)
t->tht_type = type;
t->tht_source_type = source_type;
t->tht_refcount = 1;
t->tht_enabled = 1;
streaming_pad_init(&t->tht_streaming_pad);
@ -533,41 +533,31 @@ transport_stream_find(th_transport_t *t, int pid)
*
*/
void
transport_map_channel(th_transport_t *t, channel_t *ch)
transport_map_channel(th_transport_t *t, channel_t *ch, int save)
{
lock_assert(&global_lock);
assert(t->tht_ch == NULL);
if(ch == NULL) {
if(t->tht_chname == NULL)
return;
ch = channel_find_by_name(t->tht_chname, 1);
} else {
free(t->tht_chname);
t->tht_chname = strdup(ch->ch_name);
if(t->tht_ch != NULL) {
t->tht_ch = NULL;
LIST_REMOVE(t, tht_ch_link);
}
avgstat_init(&t->tht_cc_errors, 3600);
avgstat_init(&t->tht_rate, 10);
assert(t->tht_identifier != NULL);
t->tht_ch = ch;
if(ch != NULL) {
LIST_INSERT_HEAD(&ch->ch_transports, t, tht_ch_link);
}
avgstat_init(&t->tht_cc_errors, 3600);
avgstat_init(&t->tht_rate, 10);
/**
*
*/
void
transport_unmap_channel(th_transport_t *t)
{
lock_assert(&global_lock);
t->tht_ch = ch;
LIST_INSERT_HEAD(&ch->ch_transports, t, tht_ch_link);
}
t->tht_ch = NULL;
LIST_REMOVE(t, tht_ch_link);
if(!save)
return;
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t); // Save config
pthread_mutex_unlock(&t->tht_stream_mutex);
}
@ -602,7 +592,7 @@ static struct strtab stypetab[] = {
const char *
transport_servicetype_txt(th_transport_t *t)
{
return val2str(t->tht_servicetype, stypetab);
return val2str(t->tht_servicetype, stypetab) ?: "Other";
}
/**
@ -618,14 +608,6 @@ transport_is_tv(th_transport_t *t)
t->tht_servicetype == ST_AC_HDTV;
}
/**
*
*/
int
transport_is_available(th_transport_t *t)
{
return transport_servicetype_txt(t) && LIST_FIRST(&t->tht_components);
}
/**
*
@ -719,3 +701,18 @@ transport_feed_status_to_text(transport_feed_status_t status)
}
/**
*
*/
void
transport_set_enable(th_transport_t *t, int enabled)
{
if(t->tht_enabled == enabled)
return;
t->tht_enabled = enabled;
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_config_change(t); // Save config
pthread_mutex_unlock(&t->tht_stream_mutex);
}

View file

@ -36,9 +36,7 @@ void transport_ref(th_transport_t *t);
th_transport_t *transport_find_by_identifier(const char *identifier);
void transport_map_channel(th_transport_t *t, channel_t *ch);
void transport_unmap_channel(th_transport_t *t);
void transport_map_channel(th_transport_t *t, channel_t *ch, int save);
th_transport_t *transport_find(channel_t *ch, unsigned int weight);
@ -55,8 +53,6 @@ const char *transport_servicetype_txt(th_transport_t *t);
int transport_is_tv(th_transport_t *t);
int transport_is_available(th_transport_t *t);
void transport_destroy(th_transport_t *t);
void transport_set_feed_status(th_transport_t *t,
@ -80,5 +76,6 @@ transport_find_stream_by_pid(th_transport_t *t, int pid)
htsmsg_t *transport_build_stream_start_msg(th_transport_t *t);
void transport_set_enable(th_transport_t *t, int enabled);
#endif /* TRANSPORTS_H */

View file

@ -348,7 +348,7 @@ typedef enum {
* A Transport (or in MPEG TS terms: a 'service')
*/
typedef struct th_transport {
const char *tht_name;
LIST_ENTRY(th_transport) tht_hash_link;
@ -422,11 +422,11 @@ typedef struct th_transport {
uint16_t tht_pmt_pid;
/**
* Set if transport is disabled. If disabled it should not be
* considered when chasing for available transports during
* Set if transport is enabled (the default). If disabled it should
* not be considered when chasing for available transports during
* subscription scheduling.
*/
int tht_disabled;
int tht_enabled;
LIST_ENTRY(th_transport) tht_mux_link;
@ -497,7 +497,6 @@ typedef struct th_transport {
*/
LIST_ENTRY(th_transport) tht_ch_link;
struct channel *tht_ch;
char *tht_chname;
/**
* Service probe, see serviceprobe.c for details

View file

@ -107,6 +107,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
/**
* Load all components
*/
extjs_load(hq, "static/app/comet.js");
extjs_load(hq, "static/app/tableeditor.js");
extjs_load(hq, "static/app/cteditor.js");
extjs_load(hq, "static/app/acleditor.js");
@ -167,13 +168,13 @@ page_about(http_connection_t *hc, const char *remain, void *opaque)
"<div class=\"about-title\">"
"HTS Tvheadend %s"
"</div><br>"
"&copy; 2006 - 2008 Andreas \303\226man, et al.<br><br>"
"&copy; 2006 - 2009 Andreas \303\226man, et al.<br><br>"
"<img src=\"docresources/tvheadendlogo.png\"><br>"
"<a href=\"http://hts.lonelycoder.com/\">"
"http://hts.lonelycoder.com/</a><br><br>"
"Based on software from "
"<a href=\"http://www.ffmpeg.org/\">FFmpeg</a> and "
"<a href=\"http://www.extjs.org/\">ExtJS</a>.<br>"
"<a href=\"http://www.extjs.com/\">ExtJS</a>.<br>"
"<br>"
"Build: %s"
"</center>",
@ -329,102 +330,6 @@ extjs_ecglist(http_connection_t *hc, const char *remain, void *opaque)
}
/**
*
*/
static void
extjs_dvbtree_node(htsmsg_t *array, int leaf, const char *id, const char *name,
const char *type, const char *status, int quality,
const char *itype)
{
htsmsg_t *e = htsmsg_create_map();
htsmsg_add_str(e, "uiProvider", "col");
htsmsg_add_str(e, "id", id);
htsmsg_add_u32(e, "leaf", leaf);
htsmsg_add_str(e, "itype", itype);
htsmsg_add_str(e, "name", name);
htsmsg_add_str(e, "type", type);
htsmsg_add_str(e, "status", status);
htsmsg_add_u32(e, "quality", quality);
htsmsg_add_msg(array, NULL, e);
}
/**
*
*/
static int
extjs_dvbtree(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *s = http_arg_get(&hc->hc_req_args, "node");
htsmsg_t *out = NULL;
char buf[200];
th_dvb_adapter_t *tda;
th_dvb_mux_instance_t *tdmi;
th_transport_t *t;
if(s == NULL)
return HTTP_STATUS_BAD_REQUEST;
out = htsmsg_create_list();
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_ADMIN)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
if(!strcmp(s, "root")) {
/**
* List of all adapters
*/
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) {
snprintf(buf, sizeof(buf), "%s adapter",
dvb_adaptertype_to_str(tda->tda_type));
extjs_dvbtree_node(out, 0,
tda->tda_identifier, tda->tda_displayname,
buf, tda->tda_rootpath != NULL ? "OK" : "No H/W",
100, "adapter");
}
} else if((tda = dvb_adapter_find_by_identifier(s)) != NULL) {
RB_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
dvb_mux_nicename(buf, sizeof(buf), tdmi);
extjs_dvbtree_node(out, 0,
tdmi->tdmi_identifier, buf, "DVB Mux",
dvb_mux_status(tdmi),
tdmi->tdmi_quality, "mux");
}
} else if((tdmi = dvb_mux_find_by_identifier(s)) != NULL) {
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
if(transport_servicetype_txt(t) == NULL)
continue;
extjs_dvbtree_node(out, 1,
t->tht_identifier, t->tht_svcname,
transport_servicetype_txt(t),
t->tht_ch ? "Mapped" : "Unmapped",
100, "transport");
}
}
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
@ -479,85 +384,6 @@ json_single_record(htsmsg_t *rec, const char *root)
}
/**
*
*/
static int
extjs_dvbadapter(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *s = http_arg_get(&hc->hc_req_args, "adapterId");
const char *op = http_arg_get(&hc->hc_req_args, "op");
th_dvb_adapter_t *tda = s ? dvb_adapter_find_by_identifier(s) : NULL;
th_dvb_mux_instance_t *tdmi;
th_transport_t *t;
htsmsg_t *r, *out;
if(tda == NULL)
return HTTP_STATUS_BAD_REQUEST;
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_ADMIN)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
if(!strcmp(op, "load")) {
r = htsmsg_create_map();
htsmsg_add_str(r, "id", tda->tda_identifier);
htsmsg_add_str(r, "device", tda->tda_rootpath ?: "No hardware attached");
htsmsg_add_str(r, "name", tda->tda_displayname);
htsmsg_add_u32(r, "automux", tda->tda_autodiscovery);
out = json_single_record(r, "dvbadapters");
} else if(!strcmp(op, "save")) {
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL)
dvb_adapter_set_displayname(tda, s);
if((s = http_arg_get(&hc->hc_req_args, "automux")) != NULL)
dvb_adapter_set_auto_discovery(tda, 1);
else
dvb_adapter_set_auto_discovery(tda, 0);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "addnetwork")) {
if((s = http_arg_get(&hc->hc_req_args, "network")) != NULL)
dvb_mux_preconf_add_network(tda, s);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "serviceprobe")) {
tvhlog(LOG_NOTICE, "web interface",
"Service probe started on \"%s\"", tda->tda_displayname);
RB_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
serviceprobe_enqueue(t);
}
}
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
@ -573,7 +399,7 @@ build_transport_msg(th_transport_t *t)
char subtitles[200];
char scrambling[200];
htsmsg_add_u32(r, "enabled", !t->tht_disabled);
htsmsg_add_u32(r, "enabled", t->tht_enabled);
htsmsg_add_str(r, "name", t->tht_svcname);
htsmsg_add_str(r, "provider", t->tht_provider ?: "");
@ -1236,6 +1062,459 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
}
/**
*
*/
static int
extjs_dvbadapter(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
th_dvb_adapter_t *tda;
htsmsg_t *out, *array, *r;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *s, *sc;
th_dvb_mux_instance_t *tdmi;
th_transport_t *t;
pthread_mutex_lock(&global_lock);
if(remain == NULL) {
/* Just list all adapters */
array = htsmsg_create_list();
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
htsmsg_add_msg(array, NULL, dvb_adapter_build_msg(tda));
pthread_mutex_unlock(&global_lock);
out = htsmsg_create_map();
htsmsg_add_msg(out, "entries", array);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
if((tda = dvb_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
if(!strcmp(op, "load")) {
r = htsmsg_create_map();
htsmsg_add_str(r, "id", tda->tda_identifier);
htsmsg_add_str(r, "device", tda->tda_rootpath ?: "No hardware attached");
htsmsg_add_str(r, "name", tda->tda_displayname);
htsmsg_add_u32(r, "automux", tda->tda_autodiscovery);
htsmsg_add_u32(r, "idlescan", tda->tda_idlescan);
htsmsg_add_u32(r, "logging", tda->tda_logging);
out = json_single_record(r, "dvbadapters");
} else if(!strcmp(op, "save")) {
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL)
dvb_adapter_set_displayname(tda, s);
s = http_arg_get(&hc->hc_req_args, "automux");
dvb_adapter_set_auto_discovery(tda, !!s);
s = http_arg_get(&hc->hc_req_args, "idlescan");
dvb_adapter_set_idlescan(tda, !!s);
s = http_arg_get(&hc->hc_req_args, "logging");
dvb_adapter_set_logging(tda, !!s);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "addnetwork")) {
sc = http_arg_get(&hc->hc_req_args, "satconf");
if((s = http_arg_get(&hc->hc_req_args, "network")) != NULL)
dvb_mux_preconf_add_network(tda, s, sc);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "serviceprobe")) {
tvhlog(LOG_NOTICE, "web interface",
"Service probe started on \"%s\"", tda->tda_displayname);
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
if(t->tht_enabled)
serviceprobe_enqueue(t);
}
}
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static void
mux_update(htsmsg_t *in)
{
htsmsg_field_t *f;
htsmsg_t *c;
th_dvb_mux_instance_t *tdmi;
uint32_t u32;
const char *id;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
(id = htsmsg_get_str(c, "id")) == NULL)
continue;
if((tdmi = dvb_mux_find_by_identifier(id)) == NULL)
continue;
if(!htsmsg_get_u32(c, "enabled", &u32))
dvb_mux_set_enable(tdmi, u32);
}
}
/**
*
*/
static void
mux_delete(htsmsg_t *in)
{
htsmsg_field_t *f;
th_dvb_mux_instance_t *tdmi;
const char *id;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((id = htsmsg_field_get_string(f)) != NULL &&
(tdmi = dvb_mux_find_by_identifier(id)) != NULL)
dvb_mux_destroy(tdmi);
}
}
/**
*
*/
static int
extjs_dvbmuxes(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
th_dvb_adapter_t *tda;
htsmsg_t *out, *array, *in;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
th_dvb_mux_instance_t *tdmi;
pthread_mutex_lock(&global_lock);
if(remain == NULL ||
(tda = dvb_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
out = htsmsg_create_map();
if(!strcmp(op, "get")) {
array = htsmsg_create_list();
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link)
htsmsg_add_msg(array, NULL, dvb_mux_build_msg(tdmi));
htsmsg_add_msg(out, "entries", array);
} else if(!strcmp(op, "update")) {
if(in != NULL)
mux_update(in);
out = htsmsg_create_map();
} else if(!strcmp(op, "delete")) {
if(in != NULL)
mux_delete(in);
out = htsmsg_create_map();
} else {
pthread_mutex_unlock(&global_lock);
if(in != NULL)
htsmsg_destroy(in);
htsmsg_destroy(out);
return HTTP_STATUS_BAD_REQUEST;
}
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
if(in != NULL)
htsmsg_destroy(in);
return 0;
}
/**
*
*/
static void
transport_update(htsmsg_t *in)
{
htsmsg_field_t *f;
htsmsg_t *c;
th_transport_t *t;
uint32_t u32;
const char *id;
const char *chname;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
(id = htsmsg_get_str(c, "id")) == NULL)
continue;
if((t = transport_find_by_identifier(id)) == NULL)
continue;
if(!htsmsg_get_u32(c, "enabled", &u32))
transport_set_enable(t, u32);
if((chname = htsmsg_get_str(c, "channelname")) != NULL)
transport_map_channel(t, channel_find_by_name(chname, 1), 0);
}
}
/**
*
*/
static int
transportcmp(const void *A, const void *B)
{
th_transport_t *a = *(th_transport_t **)A;
th_transport_t *b = *(th_transport_t **)B;
return strcasecmp(a->tht_svcname ?: "\0377", b->tht_svcname ?: "\0377");
}
/**
*
*/
static int
extjs_dvbservices(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
th_dvb_adapter_t *tda;
htsmsg_t *out, *array, *in;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
th_dvb_mux_instance_t *tdmi;
th_transport_t *t, **tvec;
int count = 0, i = 0;
pthread_mutex_lock(&global_lock);
if(remain == NULL ||
(tda = dvb_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
if(!strcmp(op, "get")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
count++;
}
}
tvec = alloca(sizeof(th_transport_t *) * count);
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
tvec[i++] = t;
}
}
qsort(tvec, count, sizeof(th_transport_t *), transportcmp);
for(i = 0; i < count; i++)
htsmsg_add_msg(array, NULL, dvb_transport_build_msg(tvec[i]));
htsmsg_add_msg(out, "entries", array);
} else if(!strcmp(op, "update")) {
if(in != NULL)
transport_update(in);
out = htsmsg_create_map();
} else {
pthread_mutex_unlock(&global_lock);
htsmsg_destroy(in);
return HTTP_STATUS_BAD_REQUEST;
}
htsmsg_destroy(in);
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static int
extjs_lnbtypes(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out;
out = htsmsg_create_map();
htsmsg_add_msg(out, "entries", dvb_lnblist_get());
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static int
extjs_dvbsatconf(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
th_dvb_adapter_t *tda;
htsmsg_t *out;
pthread_mutex_lock(&global_lock);
if(remain == NULL ||
(tda = dvb_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
out = htsmsg_create_map();
htsmsg_add_msg(out, "entries", dvb_satconf_list(tda));
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static int
extjs_dvbservicedetails(http_connection_t *hc,
const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *streams, *c;
th_transport_t *t;
th_stream_t *st;
char buf[20];
pthread_mutex_lock(&global_lock);
if(remain == NULL || (t = transport_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
streams = htsmsg_create_list();
LIST_FOREACH(st, &t->tht_components, st_link) {
c = htsmsg_create_map();
htsmsg_add_u32(c, "pid", st->st_pid);
htsmsg_add_str(c, "type", streaming_component_type2txt(st->st_type));
switch(st->st_type) {
default:
htsmsg_add_str(c, "details", "");
break;
case SCT_CA:
htsmsg_add_str(c, "details", psi_caid2name(st->st_caid));
break;
case SCT_AC3:
case SCT_AAC:
case SCT_MPEG2AUDIO:
htsmsg_add_str(c, "details", st->st_lang);
break;
case SCT_MPEG2VIDEO:
case SCT_H264:
buf[0] = 0;
if(st->st_frame_duration)
snprintf(buf, sizeof(buf), "%2.2f Hz",
90000.0 / st->st_frame_duration);
htsmsg_add_str(c, "details", buf);
break;
}
htsmsg_add_msg(streams, NULL, c);
}
out = htsmsg_create_map();
htsmsg_add_str(out, "title", t->tht_svcname ?: "unnamed transport");
htsmsg_add_msg(out, "streams", streams);
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
* WEB user interface
*/
@ -1245,8 +1524,6 @@ extjs_start(void)
http_path_add("/about.html", NULL, page_about, ACCESS_WEB_INTERFACE);
http_path_add("/extjs.html", NULL, extjs_root, ACCESS_WEB_INTERFACE);
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
http_path_add("/dvbtree", NULL, extjs_dvbtree, ACCESS_WEB_INTERFACE);
http_path_add("/dvbadapter", NULL, extjs_dvbadapter, ACCESS_WEB_INTERFACE);
http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE);
http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE);
http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE);
@ -1256,4 +1533,22 @@ extjs_start(void)
http_path_add("/dvr", NULL, extjs_dvr, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist", NULL, extjs_dvrlist, ACCESS_WEB_INTERFACE);
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
http_path_add("/dvb/adapter",
NULL, extjs_dvbadapter, ACCESS_ADMIN);
http_path_add("/dvb/muxes",
NULL, extjs_dvbmuxes, ACCESS_ADMIN);
http_path_add("/dvb/services",
NULL, extjs_dvbservices, ACCESS_ADMIN);
http_path_add("/dvb/lnbtypes",
NULL, extjs_lnbtypes, ACCESS_ADMIN);
http_path_add("/dvb/satconf",
NULL, extjs_dvbsatconf, ACCESS_ADMIN);
http_path_add("/dvb/servicedetails",
NULL, extjs_dvbservicedetails, ACCESS_ADMIN);
}

View file

@ -5,21 +5,35 @@
tvheadend.channelTags = new Ext.data.JsonStore({
autoLoad:true,
root:'entries',
fields: [{name: 'identifier'}, {name: 'name'}],
fields: ['identifier', 'name'],
id: 'identifier',
url:'channeltags',
baseParams: {op: 'listTags'}
});
tvheadend.comet.on('channeltags', function(m) {
if(m.reload != null)
tvheadend.channelTags.reload();
});
/**
* Channels
*/
tvheadend.channels = new Ext.data.JsonStore({
autoLoad: true,
root:'entries',
fields: [{name: 'name'}, {name: 'chid'}],
fields: ['name', 'chid'],
id: 'chid',
url: "chlist"
});
tvheadend.comet.on('channels', function(m) {
if(m.reload != null)
tvheadend.channels.reload();
});
/**
* Channel details
*/

View file

@ -0,0 +1,55 @@
/**
* Comet interfaces
*/
Ext.extend(Comet = function() {
this.addEvents({
accessUpdate: true,
dvbAdapter: true,
dvbMux: true,
dvbStore: true,
dvbSatConf: true,
logmessage: true,
channeltags: true,
autorec: true,
dvrdb: true,
channels: true,
})
}, Ext.util.Observable);
tvheadend.comet = new Comet();
tvheadend.cometPoller = function() {
function parse_comet_response(responsetxt) {
response = Ext.util.JSON.decode(responsetxt);
for(x = 0; x < response.messages.length; x++) {
m = response.messages[x];
tvheadend.comet.fireEvent(m.notificationClass, m);
}
Ext.Ajax.request({
url: '/comet',
params : { boxid: response.boxid },
success: function(result, request) {
parse_comet_response(result.responseText);
},
failure: function(result, request) {
tvheadend.log('Connection to server lost' +
', please reload user interface',
'font-weight: bold; color: #f00');
}
});
};
Ext.Ajax.request({
url: '/comet',
success: function(result, request) {
parse_comet_response(result.responseText);
}
});
}

File diff suppressed because it is too large Load diff

View file

@ -284,6 +284,11 @@ tvheadend.dvr = function() {
remoteSort: true
});
tvheadend.comet.on('dvrdb', function(m) {
if(m.reload != null)
tvheadend.dvrStore.reload();
});
tvheadend.autorecRecord = Ext.data.Record.create([
'enabled','title','channel','tag','creator','contentgrp','comment'
@ -299,6 +304,12 @@ tvheadend.dvr = function() {
baseParams: {table: "autorec", op: "get"}
});
tvheadend.comet.on('autorec', function(m) {
if(m.reload != null)
tvheadend.autorecStore.reload();
});
var panel = new Ext.TabPanel({
activeTab:0,
autoScroll:true,

View file

@ -7,27 +7,6 @@
*/
.x-column-tree .x-tree-node {
zoom:1;
}
.x-column-tree .x-tree-node-el {
/*border-bottom:1px solid #eee; borders? */
zoom:1;
}
.x-column-tree .x-tree-selected {
background: #d9e8fb;
}
.x-column-tree .x-tree-node a {
line-height:18px;
vertical-align:middle;
}
.x-column-tree .x-tree-node a span{
}
.x-column-tree .x-tree-node .x-tree-selected a span{
background:transparent;
color:#000;
}
.x-tree-col {
float:left;
overflow:hidden;
@ -85,6 +64,56 @@
}
.x-grid3-progresscol .x-grid3-cell-inner {
padding: 0px 0px 0px 5px;
}
.x-grid3-progresscol .x-progress-bar {
height: 16px;
}
.x-grid3-progresscol .x-progress-inner {
height: 16px;
}
.x-grid3-progresscol .x-progress-text-front-ie6 {
padding: 2.5px 5px;
}
.x-grid3-progresscol .x-progress-text-front {
padding: 2px 5px;
}
.x-progress-bar-red,.x-progress-bar-orange,.x-progress-bar-green {
border-bottom: 1px solid #7fa9e4;
float: left;
height: 16px;
}
.x-progress-bar-red {
background: #ff0000 url(../icons/progress-bg-red.gif) repeat-x scroll left
center;
border-top: 1px solid #ecb7ad;
}
.x-progress-bar-orange {
background: #9cbfee url(../icons/progress-bg-orange.gif) repeat-x scroll
left center;
border-right: 1px solid #deab7e;
border-top: 1px solid #d7b290;
}
.x-progress-bar-green {
background: #00ff00 url(../icons/progress-bg-green.gif) repeat-x scroll
left center;
border-right: 1px solid #5bd976;
border-top: 1px solid #79e18f;
}
.tvh-grid-unset {
color: #888;
font-style:italic;
}
.add {
background-image:url(../icons/add.gif) !important;
@ -101,6 +130,12 @@
.rec {
background-image:url(../icons/rec.png) !important;
}
.info {
background-image:url(../icons/information.png) !important;
}
.undo {
background-image:url(../icons/undo.png) !important;
}
.x-smallhdr {
float:left;
@ -127,6 +162,11 @@
}
.hts-t-info {
float:left;
width:100px;
}
.hts-doc-text {
@ -155,3 +195,82 @@
font:normal 24px verdana;
font-weight: bold;
}
/** vim: ts=4:sw=4:nu:fdc=4:nospell
*
* Ext.ux.grid.RowActions.css
*
* Style sheets for Grid RowActions Plugin
*
* @author Ing. Jozef Sakáloš
* @date 27. March 2008
* @verson $Id: Ext.ux.grid.RowActions.css 140 2008-04-06 01:24:10Z jozo $
*
* @license Ext.ux.grid.RowActions.css is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
/* styles for rows */
.ux-row-action-cell .x-grid3-cell-inner {
padding:1px 0 0 0;
}
.ux-row-action-item {
float:left;
min-width:16px;
height:16px;
background-repeat:no-repeat;
margin: 0 5px 0 0;
cursor:pointer;
overflow:hidden;
}
.ext-ie .ux-row-action-item {
width:16px;
}
.ext-ie .ux-row-action-text {
width:auto;
}
.ux-row-action-item span {
vertical-align:middle;
padding:0 0 0 20px;
line-height:18px;
}
.ext-ie .ux-row-action-item span {
width:auto;
}
/* styles for groups */
.x-grid-group-hd div {
position:relative;
height:16px;
}
.ux-grow-action-item {
min-width:16px;
height:16px;
background-repeat:no-repeat;
background-position: 0 50% ! important;
margin: 0 0 0 4px;
padding: 0 ! important;
cursor:pointer;
float:left;
}
.ext-ie .ux-grow-action-item {
width:16px;
}
.ux-action-right {
float:right;
margin: 0 3px 0 2px;
padding: 0 ! important;
}
.ux-grow-action-text {
padding: 0 ! important;
margin:0 ! important;
background:transparent none ! important;
float:left;
}
/* eof */

View file

@ -46,107 +46,8 @@ Ext.grid.CheckColumn.prototype ={
/**
* ColumnTree
* Rowexpander
*/
Ext.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {
lines:false,
borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
cls:'x-column-tree',
onRender : function(){
Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
this.headers = this.body.createChild(
{cls:'x-tree-headers'},this.innerCt.dom);
var cols = this.columns, c;
var totalWidth = 0;
for(var i = 0, len = cols.length; i < len; i++){
c = cols[i];
totalWidth += c.width;
this.headers.createChild({
cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
cn: {
cls:'x-tree-hd-text',
html: c.header
},
style:'width:'+(c.width-this.borderWidth)+'px;'
});
}
this.headers.createChild({cls:'x-clear'});
// prevent floats from wrapping when clipped
this.headers.setWidth(totalWidth);
this.innerCt.setWidth(totalWidth);
}
});
Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
focus: Ext.emptyFn, // prevent odd scrolling behavior
setColText : function(colidx, text) {
this.colNode[colidx].innerHTML = text;
},
renderElements : function(n, a, targetNode, bulkRender){
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
this.colNode = [];
var t = n.getOwnerTree();
var cols = t.columns;
var bw = t.borderWidth;
var c = cols[0];
var buf = [
'<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
'<div class="x-tree-col" style="width:',c.width-bw,'px;">',
'<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',
'<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
'<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",
"</div>"];
for(var i = 1, len = cols.length; i < len; i++){
c = cols[i];
buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
'<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",
"</div>");
}
buf.push(
'<div class="x-clear"></div></div>',
'<ul class="x-tree-node-ct" style="display:none;"></ul>',
"</li>");
if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
n.nextSibling.ui.getEl(), buf.join(""));
}else{
this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
}
this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.firstChild.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
this.anchor = cs[3];
this.textNode = cs[3].firstChild;
for(var i = 1, len = cols.length; i < len; i++) {
this.colNode[i] = this.elNode.childNodes[i].firstChild;
}
}
});
Ext.grid.RowExpander = function(config){
Ext.apply(this, config);
@ -1201,3 +1102,504 @@ Ext.ux.Multiselect = Ext.extend(Ext.form.Field, {
});
Ext.reg("multiselect", Ext.ux.Multiselect);
/**
* Ext.ux.grid.ProgressColumn - Ext.ux.grid.ProgressColumn is a grid plugin that
* shows a progress bar for a number between 0 and 100 to indicate some sort of
* progress. The plugin supports all the normal cell/column operations including
* sorting, editing, dragging, and hiding. It also supports special progression
* coloring or standard Ext.ProgressBar coloring for the bar.
*
* @author Benjamin Runnels <kraven@kraven.org>
* @copyright (c) 2008, by Benjamin Runnels
* @date 06 June 2008
* @version 1.1
*
* @license Ext.ux.grid.ProgressColumn is licensed under the terms of the Open
* Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source
* or Commercially licensed development library or toolkit without
* explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
Ext.namespace('Ext.ux.grid');
Ext.ux.grid.ProgressColumn = function(config) {
Ext.apply(this, config);
this.renderer = this.renderer.createDelegate(this);
this.addEvents('action');
Ext.ux.grid.ProgressColumn.superclass.constructor.call(this);
};
Ext.extend(Ext.ux.grid.ProgressColumn, Ext.util.Observable, {
/**
* @cfg {String} colored determines whether use special progression coloring
* or the standard Ext.ProgressBar coloring for the bar (defaults to
* false)
*/
textPst : '%',
/**
* @cfg {String} colored determines whether use special progression coloring
* or the standard Ext.ProgressBar coloring for the bar (defaults to
* false)
*/
colored : false,
/**
* @cfg {String} actionEvent Event to trigger actions, e.g. click, dblclick,
* mouseover (defaults to 'dblclick')
*/
actionEvent : 'dblclick',
init : function(grid) {
this.grid = grid;
this.view = grid.getView();
if (this.editor && grid.isEditor) {
var cfg = {
scope : this
};
cfg[this.actionEvent] = this.onClick;
grid.afterRender = grid.afterRender.createSequence(function() {
this.view.mainBody.on(cfg);
}, this);
}
},
onClick : function(e, target) {
var rowIndex = e.getTarget('.x-grid3-row').rowIndex;
var colIndex = this.view.findCellIndex(target.parentNode.parentNode);
var t = e.getTarget('.x-progress-text');
if (t) {
this.grid.startEditing(rowIndex, colIndex);
}
},
renderer : function(v, p, record) {
var style = '';
var textClass = (v < 55) ? 'x-progress-text-back' : 'x-progress-text-front' + (Ext.isIE6 ? '-ie6' : '');
//ugly hack to deal with IE6 issue
var text = String.format('</div><div class="x-progress-text {0}" style="width:100%;" id="{1}">{2}</div></div>',
textClass, Ext.id(), v + this.textPst
);
text = (v<96) ? text.substring(0, text.length - 6) : text.substr(6);
if (this.colored == true) {
if (v <= 100 && v > 66)
style = '-green';
if (v < 67 && v > 33)
style = '-orange';
if (v < 34)
style = '-red';
}
p.css += ' x-grid3-progresscol';
return String.format(
'<div class="x-progress-wrap"><div class="x-progress-inner"><div class="x-progress-bar{0}" style="width:{1}%;">{2}</div>' +
'</div>', style, v, text
);
}
});
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
* RowActions plugin for Ext grid
*
* Contains renderer for icons and fires events when an icon is clicked
*
* @author Ing. Jozef Sakáloš
* @date 22. March 2008
* @version $Id: Ext.ux.grid.RowActions.js 150 2008-04-08 21:50:58Z jozo $
*
* @license Ext.ux.grid.RowActions is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
/*global Ext */
Ext.ns('Ext.ux.grid');
/**
* @class Ext.ux.grid.RowActions
* @extends Ext.util.Observable
*
* CSS rules from Ext.ux.RowActions.css are mandatory
*
* Important general information: Actions are identified by iconCls. Wherever an <i>action</i>
* is referenced (event argument, callback argument), the iconCls of clicked icon is used.
* In another words, action identifier === iconCls.
*
* Creates new RowActions plugin
* @constructor
* @param {Object} config The config object
*/
Ext.ux.grid.RowActions = function(config) {
Ext.apply(this, config);
// {{{
this.addEvents(
/**
* @event beforeaction
* Fires before action event. Return false to cancel the subsequent action event.
* @param {Ext.grid.GridPanel} grid
* @param {Ext.data.Record} record Record corresponding to row clicked
* @param {String} action Identifies the action icon clicked. Equals to icon css class name.
* @param {Integer} rowIndex Index of clicked grid row
* @param {Integer} colIndex Index of clicked grid column that contains all action icons
*/
'beforeaction'
/**
* @event action
* Fires when icon is clicked
* @param {Ext.grid.GridPanel} grid
* @param {Ext.data.Record} record Record corresponding to row clicked
* @param {String} action Identifies the action icon clicked. Equals to icon css class name.
* @param {Integer} rowIndex Index of clicked grid row
* @param {Integer} colIndex Index of clicked grid column that contains all action icons
*/
,'action'
/**
* @event beforegroupaction
* Fires before group action event. Return false to cancel the subsequent groupaction event.
* @param {Ext.grid.GridPanel} grid
* @param {Array} records Array of records in this group
* @param {String} action Identifies the action icon clicked. Equals to icon css class name.
* @param {String} groupId Identifies the group clicked
*/
,'beforegroupaction'
/**
* @event groupaction
* Fires when icon in a group header is clicked
* @param {Ext.grid.GridPanel} grid
* @param {Array} records Array of records in this group
* @param {String} action Identifies the action icon clicked. Equals to icon css class name.
* @param {String} groupId Identifies the group clicked
*/
,'groupaction'
);
// }}}
// call parent
Ext.ux.grid.RowActions.superclass.constructor.call(this);
};
Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {
// configuration options
// {{{
/**
* @cfg {Array} actions Mandatory. Array of action configuration objects. The following
* configuration options of action are recognized:
*
* - @cfg {Function} callback Optional. Function to call if the action icon is clicked.
* This function is called with same signature as action event and in its original scope.
* If you need to call it in different scope or with another signature use
* createCallback or createDelegate functions. Works for statically defined actions. Use
* callbacks configuration options for store bound actions.
*
* - @cfg {Function} cb Shortcut for callback.
*
* - @cfg {String} iconIndex Optional, however either iconIndex or iconCls must be
* configured. Field name of the field of the grid store record that contains
* css class of the icon to show. If configured, shown icons can vary depending
* of the value of this field.
*
* - @cfg {String} iconCls. css class of the icon to show. It is ignored if iconIndex is
* configured. Use this if you want static icons that are not base on the values in the record.
*
* - @cfg {Boolean} hide Optional. True to hide this action while still have a space in
* the grid column allocated to it. IMO, it doesn't make too much sense, use hideIndex instead.
*
* - @cfg (string} hideIndex Optional. Field name of the field of the grid store record that
* contains hide flag (falsie [null, '', 0, false, undefined] to show, anything else to hide).
*
* - @cfg {String} qtipIndex Optional. Field name of the field of the grid store record that
* contains tooltip text. If configured, the tooltip texts are taken from the store.
*
* - @cfg {String} tooltip Optional. Tooltip text to use as icon tooltip. It is ignored if
* qtipIndex is configured. Use this if you want static tooltips that are not taken from the store.
*
* - @cfg {String} qtip Synonym for tooltip
*
* - @cfg {String} textIndex Optional. Field name of the field of the grids store record
* that contains text to display on the right side of the icon. If configured, the text
* shown is taken from record.
*
* - @cfg {String} text Optional. Text to display on the right side of the icon. Use this
* if you want static text that are not taken from record. Ignored if textIndex is set.
*
* - @cfg {String} style Optional. Style to apply to action icon container.
*/
/**
* @cfg {String} actionEvnet Event to trigger actions, e.g. click, dblclick, mouseover (defaults to 'click')
*/
actionEvent:'click'
/**
* @cfg {Boolean} autoWidth true to calculate field width for iconic actions only.
*/
,autoWidth:true
/**
* @cfg {Array} groupActions Array of action to use for group headers of grouping grids.
* These actions support static icons, texts and tooltips same way as actions. There is one
* more action config recognized:
* - @cfg {String} align Set it to 'left' to place action icon next to the group header text.
* (defaults to undefined = icons are placed at the right side of the group header.
*/
/**
* @cfg {Object} callbacks iconCls keyed object that contains callback functions. For example:
* callbacks:{
* 'icon-open':function(...) {...}
* ,'icon-save':function(...) {...}
* }
*/
/**
* @cfg {String} header Actions column header
*/
,header:''
/**
* @cfg {Boolean} menuDisabled No sense to display header menu for this column
*/
,menuDisabled:true
/**
* @cfg {Boolean} sortable Usually it has no sense to sort by this column
*/
,sortable:false
/**
* @cfg {String} tplGroup Template for group actions
* @private
*/
,tplGroup:
'<tpl for="actions">'
+'<div class="ux-grow-action-item<tpl if="\'right\'===align"> ux-action-right</tpl> '
+'{cls}" style="{style}" qtip="{qtip}">{text}</div>'
+'</tpl>'
/**
* @cfg {String} tplRow Template for row actions
* @private
*/
,tplRow:
'<div class="ux-row-action">'
+'<tpl for="actions">'
+'<div class="ux-row-action-item {cls} <tpl if="text">'
+'ux-row-action-text</tpl>" style="{hide}{style}" qtip="{qtip}">'
+'<tpl if="text"><span qtip="{qtip}">{text}</span></tpl></div>'
+'</tpl>'
+'</div>'
/**
* @private {Number} widthIntercept constant used for auto-width calculation
*/
,widthIntercept:4
/**
* @private {Number} widthSlope constant used for auto-width calculation
*/
,widthSlope:21
// }}}
// methods
// {{{
/**
* Init function
* @param {Ext.grid.GridPanel} grid Grid this plugin is in
*/
,init:function(grid) {
this.grid = grid;
// {{{
// setup template
if(!this.tpl) {
this.tpl = this.processActions(this.actions);
} // eo template setup
// }}}
// calculate width
if(this.autoWidth) {
this.width = this.widthSlope * this.actions.length + this.widthIntercept;
this.fixed = true;
}
// body click handler
var view = grid.getView();
var cfg = {scope:this};
cfg[this.actionEvent] = this.onClick;
grid.on({
render:{scope:this, fn:function() {
view.mainBody.on(cfg);
}}
});
// setup renderer
if(!this.renderer) {
this.renderer = function(value, cell, record, row, col, store) {
cell.css += (cell.css ? ' ' : '') + 'ux-row-action-cell';
return this.tpl.apply(this.getData(value, cell, record, row, col, store));
}.createDelegate(this);
}
// actions in grouping grids support
if(view.groupTextTpl && this.groupActions) {
view.interceptMouse = view.interceptMouse.createInterceptor(function(e) {
if(e.getTarget('.ux-grow-action-item')) {
return false;
}
});
view.groupTextTpl =
'<div class="ux-grow-action-text">' + view.groupTextTpl +'</div>'
+this.processActions(this.groupActions, this.tplGroup).apply()
;
}
} // eo function init
// }}}
// {{{
/**
* Returns data to apply to template. Override this if needed.
* @param {Mixed} value
* @param {Object} cell object to set some attributes of the grid cell
* @param {Ext.data.Record} record from which the data is extracted
* @param {Number} row row index
* @param {Number} col col index
* @param {Ext.data.Store} store object from which the record is extracted
* @returns {Object} data to apply to template
*/
,getData:function(value, cell, record, row, col, store) {
return record.data || {};
} // eo function getData
// }}}
// {{{
/**
* Processes actions configs and returns template.
* @param {Array} actions
* @param {String} template Optional. Template to use for one action item.
* @return {String}
* @private
*/
,processActions:function(actions, template) {
var acts = [];
// actions loop
Ext.each(actions, function(a, i) {
// save callback
if(a.iconCls && 'function' === typeof (a.callback || a.cb)) {
this.callbacks = this.callbacks || {};
this.callbacks[a.iconCls] = a.callback || a.cb;
}
// data for intermediate template
var o = {
cls:a.iconIndex ? '{' + a.iconIndex + '}' : (a.iconCls ? a.iconCls : '')
,qtip:a.qtipIndex ? '{' + a.qtipIndex + '}' : (a.tooltip || a.qtip ? a.tooltip || a.qtip : '')
,text:a.textIndex ? '{' + a.textIndex + '}' : (a.text ? a.text : '')
,hide:a.hideIndex ? '<tpl if="' + a.hideIndex + '">visibility:hidden;</tpl>' : (a.hide ? 'visibility:hidden;' : '')
,align:a.align || 'right'
,style:a.style ? a.style : ''
};
acts.push(o);
}, this); // eo actions loop
var xt = new Ext.XTemplate(template || this.tplRow);
return new Ext.XTemplate(xt.apply({actions:acts}));
} // eo function processActions
// }}}
// {{{
/**
* Grid body actionEvent event handler
* @private
*/
,onClick:function(e, target) {
var view = this.grid.getView();
var action = false;
// handle row action click
var row = e.getTarget('.x-grid3-row');
var col = view.findCellIndex(target.parentNode.parentNode);
var t = e.getTarget('.ux-row-action-item');
if(t) {
action = t.className.replace(/ux-row-action-item /, '');
if(action) {
action = action.replace(/ ux-row-action-text/, '');
action = action.trim();
}
}
if(false !== row && false !== col && false !== action) {
var record = this.grid.store.getAt(row.rowIndex);
// call callback if any
if(this.callbacks && 'function' === typeof this.callbacks[action]) {
this.callbacks[action](this.grid, record, action, row.rowIndex, col);
}
// fire events
if(true !== this.eventsSuspended && false === this.fireEvent('beforeaction', this.grid, record, action, row.rowIndex, col)) {
return;
}
else if(true !== this.eventsSuspended) {
this.fireEvent('action', this.grid, record, action, row.rowIndex, col);
}
}
// handle group action click
t = e.getTarget('.ux-grow-action-item');
if(t) {
// get groupId
var group = view.findGroup(target);
var groupId = group ? group.id.replace(/ext-gen[0-9]+-gp-/, '') : null;
// get matching records
var records;
if(groupId) {
var re = new RegExp(groupId);
records = this.grid.store.queryBy(function(r) {
return r._groupId.match(re);
});
records = records ? records.items : [];
}
action = t.className.replace(/ux-grow-action-item (ux-action-right )*/, '');
// call callback if any
if('function' === typeof this.callbacks[action]) {
this.callbacks[action](this.grid, records, action, groupId);
}
// fire events
if(true !== this.eventsSuspended && false === this.fireEvent('beforegroupaction', this.grid, records, action, groupId)) {
return false;
}
this.fireEvent('groupaction', this.grid, records, action, groupId);
}
} // eo function onClick
// }}}
});
// registre xtype
Ext.reg('rowactions', Ext.ux.grid.RowActions);
// eof

View file

@ -95,6 +95,30 @@ tvheadend.tableEditor = function(title, dtable, cm, rec, plugins, store,
disabled: true
});
var saveBtn = new Ext.Toolbar.Button({
tooltip: 'Save any changes made (Changed cells have red borders)',
iconCls:'save',
text: "Save changes",
handler: saveChanges,
disabled: true
});
var rejectBtn = new Ext.Toolbar.Button({
tooltip: 'Revert any changes made (Changed cells have red borders)',
iconCls:'undo',
text: "Revert changes",
handler: function() {
store.rejectChanges();
},
disabled: true
});
store.on('update', function(s, r, o) {
d = s.getModifiedRecords().length == 0
saveBtn.setDisabled(d);
rejectBtn.setDisabled(d);
});
selModel.on('selectionchange', function(self) {
if(self.getCount() > 0) {
delButton.enable();
@ -119,12 +143,7 @@ tvheadend.tableEditor = function(title, dtable, cm, rec, plugins, store,
iconCls:'add',
text: 'Add entry',
handler: addRecord
}, '-', delButton, '-', {
tooltip: 'Save any changes made (Changed cells have red borders).',
iconCls:'save',
text: "Save changes",
handler: saveChanges
}, '->', {
}, '-', delButton, '-', saveBtn, rejectBtn, '->', {
text: 'Help',
handler: function() {
new tvheadend.help(title, helpContent);

View file

@ -36,7 +36,6 @@ tvheadend.help = function(title, pagename) {
*/
function accessUpdate(o) {
if(o.dvr == true && tvheadend.dvrpanel == null) {
tvheadend.dvrpanel = new tvheadend.dvr;
tvheadend.rootTabPanel.add(tvheadend.dvrpanel);
@ -71,103 +70,23 @@ function accessUpdate(o) {
tvheadend.rootTabPanel.doLayout();
}
/**
* Comet interfaces
*/
tvheadend.comet_poller = function() {
function parse_comet_response(responsetxt) {
var response = Ext.util.JSON.decode(responsetxt);
for (var x = 0; x < response.messages.length; x++) {
var m = response.messages[x];
switch(m.notificationClass) {
*
*/
tvheadend.log = function(msg, style) {
s = style ? '<div style="' + style + '">' : '<div>'
case 'accessUpdate':
accessUpdate(m);
break;
case 'channeltags':
if(m.reload != null)
tvheadend.channelTags.reload();
break;
case 'autorec':
if(m.asyncreload != null)
tvheadend.autorecStore.reload();
break;
case 'dvrdb':
if(m.reload != null)
tvheadend.dvrStore.reload();
break;
case 'channels':
if(m.reload != null)
tvheadend.channels.reload();
break;
case 'logmessage':
var sl = Ext.get('systemlog');
var e = Ext.DomHelper.append(sl,
'<div>' + m.logtxt + '</div>');
e.scrollIntoView(sl);
break;
case 'dvbadapter':
case 'dvbmux':
case 'dvbtransport':
var n = tvheadend.dvbtree.getNodeById(m.id);
if(n != null) {
if(m.reload != null && n.isLoaded()) {
n.reload();
}
if(m.name != null) {
n.setText(m.name);
n.attributes.name = m.name;
}
if(m.quality != null) {
n.getUI().setColText(3, m.quality);
n.attributes.quality = m.quality;
}
if(m.status != null) {
n.getUI().setColText(2, m.status);
n.attributes.status = m.status;
}
}
break;
}
}
Ext.Ajax.request({
url: '/comet',
params : { boxid: response.boxid },
success: function(result, request) {
parse_comet_response(result.responseText);
}});
};
Ext.Ajax.request({
url: '/comet',
success: function(result, request) {
parse_comet_response(result.responseText);
}});
sl = Ext.get('systemlog');
e = Ext.DomHelper.append(sl, s + '<pre>' + msg + '</pre></div>');
e.scrollIntoView('systemlog');
}
/**
*
*/
// create application
tvheadend.app = function() {
@ -176,7 +95,6 @@ tvheadend.app = function() {
// public methods
init: function() {
tvheadend.rootTabPanel = new Ext.TabPanel({
region:'center',
@ -202,8 +120,14 @@ tvheadend.app = function() {
]
});
tvheadend.comet.on('accessUpdate', accessUpdate);
tvheadend.comet.on('logmessage', function(m) {
tvheadend.log(m.logtxt);
});
new tvheadend.cometPoller;
new tvheadend.comet_poller;
Ext.QuickTips.init();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B