diff --git a/Makefile b/Makefile index 800d4ae6..b263bc68 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/debian/changelog b/debian/changelog index 29a864ed..e9f69688 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 diff --git a/src/channels.c b/src/channels.c index bedaf091..4ef4ab02 100644 --- a/src/channels.c +++ b/src/channels.c @@ -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); } diff --git a/src/dvb/diseqc.c b/src/dvb/diseqc.c index 78268514..769ca176 100644 --- a/src/dvb/diseqc.c +++ b/src/dvb/diseqc.c @@ -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) { diff --git a/src/dvb/dvb.h b/src/dvb/dvb.h index c04d3d6a..76813002 100644 --- a/src/dvb/dvb.h +++ b/src/dvb/dvb.h @@ -20,10 +20,32 @@ #define DVB_H_ #include +#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_ */ diff --git a/src/dvb/dvb_adapter.c b/src/dvb/dvb_adapter.c index ce73eb70..f517b02f 100644 --- a/src/dvb/dvb_adapter.c +++ b/src/dvb/dvb_adapter.c @@ -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)); +} diff --git a/src/dvb/dvb_fe.c b/src/dvb/dvb_fe.c index 4f0db54b..12deb501 100644 --- a/src/dvb/dvb_fe.c +++ b/src/dvb/dvb_fe.c @@ -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); } diff --git a/src/dvb/dvb_multiplex.c b/src/dvb/dvb_multiplex.c index a9856c8d..61420b3b 100644 --- a/src/dvb/dvb_multiplex.c +++ b/src/dvb/dvb_multiplex.c @@ -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)); } diff --git a/src/dvb/dvb_preconf.c b/src/dvb/dvb_preconf.c index 41c45866..787225e3 100644 --- a/src/dvb/dvb_preconf.c +++ b/src/dvb/dvb_preconf.c @@ -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; } } diff --git a/src/dvb/dvb_preconf.h b/src/dvb/dvb_preconf.h index a76f3a7e..71dbce28 100644 --- a/src/dvb/dvb_preconf.h +++ b/src/dvb/dvb_preconf.h @@ -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 */ diff --git a/src/dvb/dvb_satconf.c b/src/dvb/dvb_satconf.c new file mode 100644 index 00000000..2bee6553 --- /dev/null +++ b/src/dvb/dvb_satconf.c @@ -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 . + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; + } +} diff --git a/src/dvb/dvb_support.c b/src/dvb/dvb_support.c index 57866757..ba389a45 100644 --- a/src/dvb/dvb_support.c +++ b/src/dvb/dvb_support.c @@ -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); diff --git a/src/dvb/dvb_support.h b/src/dvb/dvb_support.h index 63b8f882..e432632d 100644 --- a/src/dvb/dvb_support.h +++ b/src/dvb/dvb_support.h @@ -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 */ diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index c0f99f3d..489b8fd7 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -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); - } diff --git a/src/dvb/dvb_transport.c b/src/dvb/dvb_transport.c index 5c66828d..a5b72fb3 100644 --- a/src/dvb/dvb_transport.c +++ b/src/dvb/dvb_transport.c @@ -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); +} diff --git a/src/psi.c b/src/psi.c index 9f9ed21b..bda49913 100644 --- a/src/psi.c +++ b/src/psi.c @@ -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")) diff --git a/src/rawtsinput.c b/src/rawtsinput.c index f0029888..7caaa139 100644 --- a/src/rawtsinput.c +++ b/src/rawtsinput.c @@ -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; } diff --git a/src/serviceprobe.c b/src/serviceprobe.c index e0b2cd39..bfb12bbb 100644 --- a/src/serviceprobe.c +++ b/src/serviceprobe.c @@ -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); } diff --git a/src/transports.c b/src/transports.c index 853379bf..8fb0d9f5 100644 --- a/src/transports.c +++ b/src/transports.c @@ -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); +} diff --git a/src/transports.h b/src/transports.h index 1a9d63f5..397f702a 100644 --- a/src/transports.h +++ b/src/transports.h @@ -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 */ diff --git a/src/tvhead.h b/src/tvhead.h index 28f490a1..aac00edb 100644 --- a/src/tvhead.h +++ b/src/tvhead.h @@ -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 diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 328845c1..d19ef112 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -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) "
" "HTS Tvheadend %s" "

" - "© 2006 - 2008 Andreas \303\226man, et al.

" + "© 2006 - 2009 Andreas \303\226man, et al.

" "
" "" "http://hts.lonelycoder.com/

" "Based on software from " "FFmpeg and " - "ExtJS.
" + "ExtJS.
" "
" "Build: %s" "", @@ -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); } diff --git a/src/webui/static/app/chconf.js b/src/webui/static/app/chconf.js index b9c915d0..7a008a8b 100644 --- a/src/webui/static/app/chconf.js +++ b/src/webui/static/app/chconf.js @@ -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 */ diff --git a/src/webui/static/app/comet.js b/src/webui/static/app/comet.js new file mode 100644 index 00000000..f84cecdf --- /dev/null +++ b/src/webui/static/app/comet.js @@ -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); + } + }); +} + + diff --git a/src/webui/static/app/dvb.js b/src/webui/static/app/dvb.js index 8af7bc51..0e433053 100644 --- a/src/webui/static/app/dvb.js +++ b/src/webui/static/app/dvb.js @@ -1,157 +1,883 @@ +/** + * Datastore for adapters + */ +tvheadend.dvbAdapterStore = new Ext.data.JsonStore({ + root:'entries', + id: 'identifier', + fields: ['identifier', + 'name', + 'path', + 'devicename', + 'currentMux', + 'services', + 'muxes', + 'initialMuxes', + 'satConf'], + url:'/dvb/adapter' +}); + +tvheadend.comet.on('dvbAdapter', function(m) { + idx = tvheadend.dvbAdapterStore.find('identifier', m.identifier); + if(idx == -1) + return; + r = tvheadend.dvbAdapterStore.getAt(idx); + + r.beginEdit(); + for (key in m) + r.set(key, m[key]); + r.endEdit(); + tvheadend.dvbAdapterStore.commitChanges(); +}); + + +/** + * DVB Mux grid + */ +tvheadend.dvb_muxes = function(adapterData, satConfStore) { + + adapterId = adapterData.identifier; + + var fm = Ext.form; + + var enabledColumn = new Ext.grid.CheckColumn({ + header: "Enabled", + dataIndex: 'enabled', + width: 40 + }); + + var qualityColumn = new Ext.ux.grid.ProgressColumn({ + header : "Quality", + dataIndex : 'quality', + width : 85, + textPst : '%', + colored : true + }); + + var cmlist = Array(); + + cmlist.push(enabledColumn, + { + header: "Network", + dataIndex: 'network', + width: 200 + }, + { + header: "Frequency", + dataIndex: 'freq', + width: 50 + }); + + if(adapterData.satConf) { + // Include DVB-S specific stuff + + satConfStore.on('update', function(s, r, c) { + if(grid.rendered) + grid.getView().refresh(); + }); + + satConfStore.on('load', function(s, r, o) { + if(grid.rendered) + grid.getView().refresh(); + }); + + tvheadend.comet.on('dvbSatConf', function(m) { + if(m.adapterId == adapterId) + satConfStore.reload(); + }); + + cmlist.push({ + header: "Polarisation", + dataIndex: 'pol', + width: 50 + }, { + header: "Satellite config", + dataIndex: 'satconf', + width: 100, + renderer: function(value, metadata, record, row, col, store) { + r = satConfStore.getById(value); + return typeof r === 'undefined' ? + 'Unset' + : r.data.name; + } + }); + } + + cmlist.push( + { + header: "MuxID", + dataIndex: 'muxid', + width: 50 + }, + qualityColumn + ); + + var cm = new Ext.grid.ColumnModel(cmlist); + cm.defaultSortable = true; + + var rec = Ext.data.Record.create([ + 'id', 'enabled','network', 'freq', 'pol', 'satconf', 'muxid', 'quality' + ]); + + var store = new Ext.data.JsonStore({ + root: 'entries', + fields: rec, + url: "dvb/muxes/" + adapterId, + autoLoad: true, + id: 'id', + baseParams: {op: "get"}, + listeners: { + 'update': function(s, r, o) { + d = s.getModifiedRecords().length == 0 + saveBtn.setDisabled(d); + rejectBtn.setDisabled(d); + } + } + }); + + tvheadend.comet.on('dvbMux', function(m) { + + r = store.getById(m.id) + if(typeof r === 'undefined') { + store.reload(); + return; + } + + for (key in m) + r.data[key] = m[key]; + + store.afterEdit(r); + store.fireEvent('updated', store, r, Ext.data.Record.COMMIT); + }); + + function delSelected() { + var selectedKeys = grid.selModel.selections.keys; + if(selectedKeys.length > 0) { + Ext.MessageBox.confirm('Message', + 'Do you really want to delete selection?', + deleteRecord); + } else { + Ext.MessageBox.alert('Message', + 'Please select at least one item to delete'); + } + }; + + + function deleteRecord(btn) { + if(btn=='yes') { + var selectedKeys = grid.selModel.selections.keys; + + Ext.Ajax.request({ + url: "dvb/muxes/" + adapterId, + params: { + op:"delete", + entries:Ext.encode(selectedKeys) + }, + failure:function(response,options) { + Ext.MessageBox.alert('Server Error','Unable to delete'); + }, + success:function(response,options) { + store.reload(); + } + }) + } + } + + function saveChanges() { + var mr = store.getModifiedRecords(); + var out = new Array(); + for (var x = 0; x < mr.length; x++) { + v = mr[x].getChanges(); + out[x] = v; + out[x].id = mr[x].id; + } + + Ext.Ajax.request({ + url: "dvb/muxes/" + adapterId, + params: { + op:"update", + entries:Ext.encode(out) + }, + success:function(response,options) { + store.commitChanges(); + }, + failure:function(response,options) { + Ext.MessageBox.alert('Message',response.statusText); + } + }); + } + + var selModel = new Ext.grid.RowSelectionModel({ + singleSelect:false + }); + + var delBtn = new Ext.Toolbar.Button({ + tooltip: 'Delete one or more selected muxes', + iconCls:'remove', + text: 'Delete selected', + handler: delSelected, + disabled: true + }); + + selModel.on('selectionchange', function(s) { + delBtn.setDisabled(s.getCount() == 0); + }); + + 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 + }); + + var grid = new Ext.grid.EditorGridPanel({ + stripeRows: true, + title: 'Multiplexes', + plugins: [enabledColumn, qualityColumn], + store: store, + clicksToEdit: 2, + cm: cm, + viewConfig: {forceFit:true}, + selModel: selModel, + tbar: [ + delBtn, '-', saveBtn, rejectBtn, '->', { + text: 'Help', + handler: function() { + new tvheadend.help(title, helpContent); + } + } + ] + }); + + return grid; +} + + +/** + * DVB service grid + */ +tvheadend.dvb_services = function(adapterId) { + + var fm = Ext.form; + + var enabledColumn = new Ext.grid.CheckColumn({ + header: "Enabled", + dataIndex: 'enabled', + width: 45 + }); + + var actions = new Ext.ux.grid.RowActions({ + header:'', + dataIndex: 'actions', + width: 45, + actions: [ + { + iconCls:'info', + qtip:'Detailed information about service', + cb: function(grid, record, action, row, col) { + Ext.Ajax.request({ + url: "dvb/servicedetails/" + record.id, + success:function(response, options) { + r = Ext.util.JSON.decode(response.responseText); + tvheadend.showTransportDetails(r); + } + }) + } + } + ] + }); + + var cm = new Ext.grid.ColumnModel([ + enabledColumn, + { + header: "Service name", + dataIndex: 'svcname', + width: 150 + }, + { + header: "Channel name", + dataIndex: 'channelname', + width: 150, + renderer: function(value, metadata, record, row, col, store) { + return value ? value : + 'Unmapped'; + }, + editor: new fm.ComboBox({ + store: tvheadend.channels, + allowBlank: true, + typeAhead: true, + minChars: 2, + lazyRender: true, + triggerAction: 'all', + mode: 'local', + displayField:'name' + }) + }, + { + header: "Type", + dataIndex: 'type', + width: 50 + }, + { + header: "Provider", + dataIndex: 'provider', + width: 150 + }, + { + header: "Network", + dataIndex: 'network', + width: 100 + }, + { + header: "Multiplex", + dataIndex: 'mux', + width: 100 + }, + { + header: "Service ID", + dataIndex: 'sid', + width: 50, + hidden: true + }, + { + header: "PMT PID", + dataIndex: 'pmt', + width: 50, + hidden: true + }, + { + header: "PCR PID", + dataIndex: 'pcr', + width: 50, + hidden: true + }, actions + ]); + + cm.defaultSortable = true; + + var store = new Ext.data.JsonStore({ + root: 'entries', + fields: Ext.data.Record.create([ + 'id', 'enabled', 'type', 'sid', 'pmt', 'pcr', + 'svcname', 'network', 'provider', 'mux', 'channelname' + ]), + url: "dvb/services/" + adapterId, + autoLoad: true, + id: 'id', + baseParams: {op: "get"}, + listeners: { + 'update': function(s, r, o) { + d = s.getModifiedRecords().length == 0 + saveBtn.setDisabled(d); + rejectBtn.setDisabled(d); + } + } + }); + + var storeReloader = new Ext.util.DelayedTask(function() { + store.reload() + }); + + tvheadend.comet.on('dvbService', function(m) { + storeReloader.delay(500); + }); + + + function delSelected() { + var selectedKeys = grid.selModel.selections.keys; + if(selectedKeys.length > 0) { + Ext.MessageBox.confirm('Message', + 'Do you really want to delete selection?', + deleteRecord); + } else { + Ext.MessageBox.alert('Message', + 'Please select at least one item to delete'); + } + }; + + + function saveChanges() { + var mr = store.getModifiedRecords(); + var out = new Array(); + for (var x = 0; x < mr.length; x++) { + v = mr[x].getChanges(); + out[x] = v; + out[x].id = mr[x].id; + } + + Ext.Ajax.request({ + url: "dvb/services/" + adapterId, + params: { + op:"update", + entries:Ext.encode(out) + }, + success:function(response,options) { + store.commitChanges(); + }, + failure:function(response,options) { + Ext.MessageBox.alert('Message',response.statusText); + } + }); + } + + 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 + }); + + var selModel = new Ext.grid.RowSelectionModel({ + singleSelect:false + }); + + var grid = new Ext.grid.EditorGridPanel({ + stripeRows: true, + title: 'Services', + plugins: [enabledColumn, actions], + store: store, + clicksToEdit: 2, + cm: cm, + viewConfig: {forceFit:true}, + selModel: selModel, + tbar: [saveBtn, rejectBtn, '->', { + text: 'Help', + handler: function() { + new tvheadend.help(title, helpContent); + } + } + ] + }); + return grid; +} + +/** + * + */ +tvheadend.addMuxByLocation = function(adapterData, satConfStore) { + + var addBtn = new Ext.Button({ + text: 'Add DVB network', + disabled: true, + handler: function() { + var n = locationList.getSelectionModel().getSelectedNode(); + Ext.Ajax.request({ + url: '/dvb/adapter/' + adapterData.identifier, + params: { + network: n.attributes.id, + satconf: satConfCombo ? satConfCombo.getValue() : null, + op: 'addnetwork' + } + }); + win.close(); + } + }); + + if(satConfStore) { + satConfCombo = new Ext.form.ComboBox({ + store: satConfStore, + width: 480, + editable: false, + allowBlank: false, + triggerAction: 'all', + mode: 'remote', + displayField:'name', + valueField:'identifier', + emptyText: 'Select satellite configuration...' + }); + } else { + satConfCombo = false; + } + + var locationList = new Ext.tree.TreePanel({ + title:'By location', + autoScroll:true, + rootVisible:false, + loader: new Ext.tree.TreeLoader({ + baseParams: {adapter: adapterData.identifier}, + dataUrl:'/dvbnetworks' + }), + + root: new Ext.tree.AsyncTreeNode({ + id:'root' + }), + + bbar: [satConfCombo], + + buttons: [addBtn], + buttonAlign: 'center' + }); + + + locationList.on('click', function(n) { + if(n.attributes.leaf) { + addBtn.enable(); + } else { + addBtn.disable(); + } + }); + + win = new Ext.Window({ + title: 'Add muxes on ' + adapterData.name, + layout: 'fit', + width: 500, + height: 500, + modal: true, + plain: true, + items: new Ext.TabPanel({ + autoTabs: true, + activeTab: 0, + deferredRender: false, + border: false, + items: locationList + }) + }); + win.show(); +} + /** * DVB adapter details */ -tvheadend.dvb_adapterdetails = function(adapterId, adapterName, treenode) { +tvheadend.dvb_adapter_general = function(adapterData, satConfStore) { + + adapterId = adapterData.identifier; + + var addMuxByLocationBtn = new Ext.Button({ + style:'margin:5px', + iconCls:'add', + text: 'Add DVB Network by location...', + handler:function() { + tvheadend.addMuxByLocation(adapterData, satConfStore); + } + }); + + var serviceScanBtn = new Ext.Button({ + style:'margin:5px', + iconCls:'option', + text: 'Map DVB services to channels...', + disabled: adapterData.services == 0 || adapterData.initialMuxes, + handler:function() { + Ext.Ajax.request({ + url:'/dvb/adapter/' + adapterId, + params: { + op: 'serviceprobe' + } + }) + } + }); + + + /* Tool panel */ + + var toolpanel = new Ext.Panel({ + layout: 'table', + title: 'Tools', + style:'margin:10px', + bodyStyle:'padding:5px', + columnWidth: .25, + layoutConfig: { + columns: 1 + }, + + items: [ + addMuxByLocationBtn, + serviceScanBtn + ] + }); + + /* Conf panel */ var confreader = new Ext.data.JsonReader({ root: 'dvbadapters' - }, ['name', 'automux']); + }, ['name', 'automux', 'idlescan', 'logging']); - function addmux() { - - var locationbutton = new Ext.Button({ - text: 'Add', - disabled: true, - handler: function() { - var n = locationlist.getSelectionModel().getSelectedNode(); - Ext.Ajax.request({ - url: '/dvbadapter', - params: {network: n.attributes.id, - adapterId: adapterId, op: 'addnetwork'}}); - win.close(); - } + function saveConfForm () { + confform.getForm().submit({ + url:'/dvb/adapter/' + adapterId, + params:{'op':'save'}, + waitMsg:'Saving Data...' }); - - var locationlist = new Ext.tree.TreePanel({ - title:'By location', - autoScroll:true, - rootVisible:false, - loader: new Ext.tree.TreeLoader({ - baseParams: {adapter: adapterId}, - dataUrl:'/dvbnetworks' - }), - - root: new Ext.tree.AsyncTreeNode({ - id:'root' - }), - - buttons: [locationbutton], - buttonAlign: 'center' - }); - - - locationlist.on('click', function(n) { - if(n.attributes.leaf) { - locationbutton.enable(); - } else { - locationbutton.disable(); - } - }); - - - win = new Ext.Window({ - title: 'Add mux(es) on ' + adapterName, - layout: 'fit', - width: 500, - height: 500, - modal: true, - plain: true, - items: new Ext.TabPanel({ - autoTabs: true, - activeTab: 0, - deferredRender: false, - border: false, - items: [locationlist, { - html: 'Not implemeted yet', - title: 'Manual configuration' - }] - }) - }); - win.show(); } - - /** - * - */ - function probeservices() { - Ext.MessageBox.confirm( - 'Message', - 'Probe all DVB services on "' + adapterName + - '" and map to TV-channels in tvheadend', - function(button) { - if(button == 'no') - return; - - Ext.Ajax.request({ - url: '/dvbadapter', - params: {adapterId: adapterId, - op: 'serviceprobe'} - }) - }); - }; - - - /** - * - */ - function saveconfig() { - panel.getForm().submit({url:'/dvbadapter', - params:{'adapterId': adapterId, 'op':'save'}, - waitMsg:'Saving Data...' - }); - }; - - - var panel = new Ext.FormPanel({ - border:false, + var confform = new Ext.FormPanel({ + title:'Adapter configuration', + columnWidth: .40, + frame:true, + border:true, disabled:true, - title: adapterName, - bodyStyle:'padding:15px', + style:'margin:10px', + bodyStyle:'padding:5px', labelAlign: 'right', - labelWidth: 150, + labelWidth: 110, waitMsgTarget: true, reader: confreader, defaultType: 'textfield', - items: [{ - fieldLabel: 'Adapter name', - name: 'name', - width: 400 - }, - - new Ext.form.Checkbox({ - fieldLabel: 'Autodetect muxes', - name: 'automux' - }) - ], - tbar:[{ - tooltip: 'Manually add new transport multiplexes', - iconCls:'add', - text: 'Add mux(es)', - handler: addmux - }, '-', { - tooltip: 'Scan all transports on this adapter and map those who has a working video stream to a channel', - iconCls:'option', - text: 'Probe services', - handler: probeservices - }, '-', { - tooltip: 'Save and changes made to the configuation below', - iconCls:'save', - text: 'Save configuration', - handler: saveconfig - }] + items: [ + { + fieldLabel: 'Adapter name', + name: 'name', + width: 250 + }, + new Ext.form.Checkbox({ + fieldLabel: 'Autodetect muxes', + name: 'automux' + }), + new Ext.form.Checkbox({ + fieldLabel: 'Idle scanning', + name: 'idlescan' + }), + new Ext.form.Checkbox({ + fieldLabel: 'Detailed logging', + name: 'logging' + }) + ], + buttons: [{ + text: 'Save', + handler: saveConfForm + }] }); - panel.getForm().load({ - url:'/dvbadapter', - params:{'adapterId': adapterId, 'op':'load'}, + confform.getForm().load({ + url:'/dvb/adapter/' + adapterId, + params:{'op':'load'}, success:function(form, action) { - panel.enable(); - }}); + confform.enable(); + } + }); + + /** + * Information / capabilities panel + */ + var infoTemplate = new Ext.Template( + '

Hardware

' + + '

Device path:

{path}' + + '

Device name:

{devicename}' + + '

Status

' + + '

Currently tuned to:

{currentMux} ' + + '

Services:

{services}' + + '

Muxes:

{muxes}' + + '

Muxes awaiting initial scan:

{initialMuxes}' + ); + + + var infoPanel = new Ext.Panel({ + title:'Information and capabilities', + columnWidth: .35, + frame:true, + border:true, + style:'margin:10px', + bodyStyle:'padding:5px', + html: infoTemplate.applyTemplate(adapterData) + }); + + /** + * Main adapter panel + */ + var panel = new Ext.Panel({ + title: 'General', + layout:'column', + items: [toolpanel, confform, infoPanel] + }); + + + /** + * Subscribe and react on updates for this adapter + */ + tvheadend.dvbAdapterStore.on('update', function(s, r, o) { + if(r.data.identifier != adapterId) + return; + infoTemplate.overwrite(infoPanel.body, r.data); + + if(r.data.services > 0 && r.data.initialMuxes == 0) + serviceScanBtn.enable(); + else + serviceScanBtn.disable(); + }); + + return panel; +} + + + +/** + * + */ +tvheadend.dvb_dummy = function(title) +{ + return new Ext.Panel({ + layout:'fit', + items:[{border: false}], + title: title + }); +} + +/** + * + */ +tvheadend.dvb_satconf = function(adapterId, lnbStore) +{ + var fm = Ext.form; + + var cm = new Ext.grid.ColumnModel([ + { + header: "Name", + dataIndex: 'name', + width: 200, + editor: new fm.TextField({allowBlank: false}) + },{ + header: "Switchport", + dataIndex: 'port', + editor: new fm.NumberField({ + minValue: 0, + maxValue: 15 + }) + },{ + header: "LNB type", + dataIndex: 'lnb', + width: 200, + editor: new fm.ComboBox({ + store: lnbStore, + editable: false, + allowBlank: false, + triggerAction: 'all', + mode: 'remote', + displayField:'identifier', + valueField:'identifier', + emptyText: 'Select LNB type...' + }) + },{ + header: "Comment", + dataIndex: 'comment', + width: 400, + editor: new fm.TextField() + } + ]); + + var rec = Ext.data.Record.create([ + 'name','port','comment','lnb' + ]); + + return new tvheadend.tableEditor('Satellite config', + 'dvbsatconf/' + adapterId, cm, rec, + null, null, null); +} + + +/** + * + */ +tvheadend.dvb_adapter = function(data) +{ + + if(data.satConf) { + var lnbStore = new Ext.data.JsonStore({ + root:'entries', + autoload:true, + fields: ['identifier'], + url:'/dvb/lnbtypes' + }); + + var satConfStore = new Ext.data.JsonStore({ + root:'entries', + autoLoad: true, + id: 'identifier', + fields: ['identifier', 'name'], + url:'/dvb/satconf/' + data.identifier + }); + } else { + satConfStore = false; + } + + var items = [ + new tvheadend.dvb_adapter_general(data, satConfStore), + new tvheadend.dvb_muxes(data, satConfStore), + new tvheadend.dvb_services(data.identifier) + ]; + + if(data.satConf) + items.push(new tvheadend.dvb_satconf(data.identifier, lnbStore)); + + var panel = new Ext.TabPanel({ + border: false, + activeTab:0, + autoScroll:true, + items: items + }); + + return panel; + +} + +/** + * + */ +tvheadend.dvb = function() +{ + + var adapterSelection = new Ext.form.ComboBox({ + loadingText: 'Loading...', + width: 300, + displayField:'name', + store: tvheadend.dvbAdapterStore, + mode: 'remote', + editable: false, + triggerAction: 'all', + emptyText: 'Select DVB adapter...' + }); + + var dummyadapter = new Ext.Panel({ + region:'center', layout:'fit', + items:[{border: false}] + }); + + + var panel = new Ext.Panel({ + title: 'DVB Adapters', + layout:'fit', + tbar: [ + adapterSelection + ], + + items: [ + dummyadapter + ] + }); + + + adapterSelection.on('select', function(c, r) { + + panel.remove(panel.getComponent(0)); + panel.doLayout(); + + var newPanel = new tvheadend.dvb_adapter(r.data) + panel.add(newPanel); + panel.doLayout(); + }); return panel; } @@ -160,98 +886,36 @@ tvheadend.dvb_adapterdetails = function(adapterId, adapterName, treenode) { /** * */ -tvheadend.dvb = function() { +tvheadend.showTransportDetails = function(data) +{ + html = ''; + console.log(data); - var tree = new Ext.tree.ColumnTree({ - region:'west', - autoScroll:true, - rootVisible:false, + html += '
'; + html += 'PID '; + html += 'Type'; + html += 'Details'; + html += '
'; + + for(i = 0; i < data.streams.length; i++) { + s = data.streams[i]; - columns:[{ - header:'Name', - width:300, - dataIndex:'name' - },{ - header:'Type', - width:100, - dataIndex:'type' - },{ - header:'Status', - width:100, - dataIndex:'status' - },{ - header:'Quality', - width:100, - dataIndex:'quality' - }], - - loader: new Ext.tree.TreeLoader({ - clearOnLoad: true, - dataUrl:'/dvbtree', - uiProviders:{ - 'col': Ext.tree.ColumnNodeUI - } - }), + html += '
'; + html += '' + s.pid + ''; + html += '' + s.type + ''; + html += '' + (s.details.length > 0 ? s.details : ' ') + ''; + html += '
'; + } - root: new Ext.tree.AsyncTreeNode({ - id:'root' - }) + win = new Ext.Window({ + title: 'Service details for ' + data.title, + layout: 'fit', + width: 400, + height: 400, + plain: true, + bodyStyle: 'padding: 5px', + html: html }); - - - /** - * - */ - - var details = new Ext.Panel({ - region:'center', layout:'fit', - items:[{border: false}] - }); - - /** - * - */ - var panel = new Ext.Panel({ - border: false, - title:'DVB Adapters', - layout:'border', - tbar: ['->', { - text: 'Help', - handler: function() { - new tvheadend.help('DVB', 'config_dvb.html'); - } - }], - items: [tree, details] - }); - - /** - * - */ - tree.on('click', function(n) { - details.remove(details.getComponent(0)); - - details.doLayout(); - - switch(n.attributes.itype) { - case 'adapter': - var newPanel = - new tvheadend.dvb_adapterdetails(n.attributes.id, - n.attributes.name, n); - break; - - case 'mux': - case 'transport': - default: - var newPanel = {title: n.attributes.name, html: ''}; - break; - } - - details.add(newPanel); - details.doLayout(); - }); - - tvheadend.dvbtree = tree; - - return panel; + win.show(); } diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 8ba03914..c78a7a75 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -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, diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css index 26be1352..b9207b07 100644 --- a/src/webui/static/app/ext.css +++ b/src/webui/static/app/ext.css @@ -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 */ diff --git a/src/webui/static/app/extensions.js b/src/webui/static/app/extensions.js index 27377212..50f421e8 100644 --- a/src/webui/static/app/extensions.js +++ b/src/webui/static/app/extensions.js @@ -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 = [ - '
  • ', - '"]; - for(var i = 1, len = cols.length; i < len; i++){ - c = cols[i]; - - buf.push('
    ', - '
    ',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"
    ", - "
    "); - } - buf.push( - '
    ', - '', - "
  • "); - - 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 + * @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('
    {2}
    ', + 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( + '
    {2}
    ' + + '
    ', 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 action + * 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: + '' + +'
    ux-action-right ' + +'{cls}" style="{style}" qtip="{qtip}">{text}
    ' + +'
    ' + + /** + * @cfg {String} tplRow Template for row actions + * @private + */ + ,tplRow: + '
    ' + +'' + +'
    ' + +'ux-row-action-text" style="{hide}{style}" qtip="{qtip}">' + +'{text}
    ' + +'
    ' + +'
    ' + + /** + * @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 = + '
    ' + view.groupTextTpl +'
    ' + +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 ? 'visibility:hidden;' : (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 diff --git a/src/webui/static/app/tableeditor.js b/src/webui/static/app/tableeditor.js index 0faca650..ad68664e 100644 --- a/src/webui/static/app/tableeditor.js +++ b/src/webui/static/app/tableeditor.js @@ -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); diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 30a9ac23..b9d61262 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -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 ? '
    ' : '
    ' - 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, - '
    ' + m.logtxt + '
    '); - 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 + '
    ' + msg + '
    '); + 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(); } diff --git a/src/webui/static/icons/information.png b/src/webui/static/icons/information.png new file mode 100644 index 00000000..12cd1aef Binary files /dev/null and b/src/webui/static/icons/information.png differ diff --git a/src/webui/static/icons/progress-bg-green.gif b/src/webui/static/icons/progress-bg-green.gif new file mode 100644 index 00000000..ab2704c7 Binary files /dev/null and b/src/webui/static/icons/progress-bg-green.gif differ diff --git a/src/webui/static/icons/progress-bg-orange.gif b/src/webui/static/icons/progress-bg-orange.gif new file mode 100644 index 00000000..edd099ea Binary files /dev/null and b/src/webui/static/icons/progress-bg-orange.gif differ diff --git a/src/webui/static/icons/progress-bg-red.gif b/src/webui/static/icons/progress-bg-red.gif new file mode 100644 index 00000000..e5750550 Binary files /dev/null and b/src/webui/static/icons/progress-bg-red.gif differ diff --git a/src/webui/static/icons/rec.png b/src/webui/static/icons/rec.png index 2386fac7..a2c63d1c 100644 Binary files a/src/webui/static/icons/rec.png and b/src/webui/static/icons/rec.png differ diff --git a/src/webui/static/icons/undo.png b/src/webui/static/icons/undo.png new file mode 100644 index 00000000..6972c5e5 Binary files /dev/null and b/src/webui/static/icons/undo.png differ