Merge pull request #1 from tvheadend/master

sync with main repository
This commit is contained in:
Mario Di Raimondo 2014-09-12 19:56:58 +02:00
commit 06782f719c
105 changed files with 8021 additions and 5497 deletions

View file

@ -156,7 +156,8 @@ SRCS += \
src/api/api_imagecache.c \
src/api/api_esfilter.c \
src/api/api_intlconv.c \
src/api/api_access.c
src/api/api_access.c \
src/api/api_dvr.c
SRCS += \
src/parsers/parsers.c \

View file

@ -68,13 +68,11 @@ The columns have the following functions:
<dd>
Enables access to all video recording functions. This also include administration of the auto recordings.
<dt>All Configs (VR)
<dt>DVR Config Profile
<dd>
Allow to use all DVR configuration profiles. If not set, a DVR
configuration profile matched to the authorized user by name is always used
(the configuration profile must have same name as the user). If the DVR
configuration profile does not exists, the default profile is used.
The user is also not allowed to select another profile.
If set, the user will only be able to use the DVR config profile
equal to this value.
Note that this field is unset when the DVR Config Profile is removed.
<dt>Web interface
<dd>
@ -84,13 +82,6 @@ The columns have the following functions:
<dd>
Enables access to the Configuration tab.
<dt>Username Channel Tag Match
<dd>
If enabled, the user will only be able to access channels with a tag the
same name as the username.
This provides a very rudimentary way of limiting access to certain channels.
<dt>Min Channel Num
<dd>
If nonzero, the user will only be able to access channels with
@ -103,11 +94,9 @@ The columns have the following functions:
<dt>Channel Tag
<dd>
If set, the user will only be able to access channels with
a channel tag equal to this value. Note that this field stores
the tag name (not identified). It means that this field would not be
updated when the tag name is changed. A manual re-set of this field
is required (for security reasons).
If set, the user will only be able to access channels containing
this channel tag.
Note that this field is unset when the channel tag is removed.
<dt>Comment
<dd>

View file

@ -0,0 +1,10 @@
<div class="hts-doc-text">
<p>
This tab is used to manipulate with the Digital Video Recorder entries -
the automatic recording.
<p>
A volunteer required to fill this...
</div>

View file

@ -0,0 +1,9 @@
<div class="hts-doc-text">
<p>
This tab is used to manipulate with the Digital Video Recorder entries.
<p>
A volunteer required to fill this...
</div>

View file

@ -17,7 +17,40 @@
<dd>Whether or not the mux is enabled and thus available.
<dt>EPG
<dd>Whether or not to retrieve EPG information from the mux.
<dd>EPG scan setup
<dl>
<dt>Disable
<dd>Disable the EPG scan
<dt>Enable (auto)
<dd>Enable the EPG scan (when some services from this mux are assigned to channels)
<dt>Force (auto)
<dd>Force the EPG scan (everytime when the EPG scan is triggered)
<dt>Only EIT
<dd>Do only EIT EPG scan (when some services from this mux are assigned to channels)
<dt>Only UK Freesat
<dd>Do only UK Freesat EPG scan (when some services from this mux are assigned to channels)
<dt>Only UK Freeview
<dd>Do only UK Freeview EPG scan (when some services from this mux are assigned to channels)
<dt>Only Viasat Baltic
<dd>Do only Viasat Baltic EPG scan (when some services from this mux are assigned to channels)
<dt>Only OpenTV Sky UK
<dd>Do only OpenTV Sky UK EPG scan (the TSID must match in the skyuk configuration file)
<dt>Only OpenTV Sky Italia
<dd>Do only OpenTV Sky Italia EPG scan (the TSID must match in the skyit configuration file)
<dt>Only OpenTV Sky Ausat
<dd>Do only OpenTV Sky Ausat EPG scan (the TSID must match in the ausat configuration file)
</dl>
<dt>Network
<dd>The name of the network to which the mux belongs. Networks are defined in Configuration -> DVB Inputs -> Networks - DVB-S or ATSC, for example.
@ -45,6 +78,11 @@
<dt>Character Set
<dd>The character encoding for this mux (e.g. UTF-8).
<dt>PMT Descriptor 0x06 = AC-3
<dd>Whether or not the empty PMT descriptor 0x06 defaults to
the AC-3 stream. Some Chinese cable providers are using
this. If unsure, keep this off.
<dt>Interface
<dd>IPTV : the network interface/card on which the IPTV source can be found.

View file

@ -37,6 +37,8 @@
#include "access.h"
#include "settings.h"
#include "channels.h"
#include "dvr/dvr.h"
#include "tcp.h"
struct access_entry_queue access_entries;
struct access_ticket_queue access_tickets;
@ -158,6 +160,11 @@ access_ticket_verify(const char *id, const char *resource)
void
access_destroy(access_t *a)
{
if (a == NULL)
return;
free(a->aa_username);
free(a->aa_representative);
htsmsg_destroy(a->aa_dvrcfgs);
htsmsg_destroy(a->aa_chtags);
free(a);
}
@ -292,10 +299,16 @@ access_update(access_t *a, access_entry_t *ae)
}
}
if(ae->ae_chtag && ae->ae_chtag[0] != '\0') {
if(ae->ae_dvr_config && ae->ae_dvr_config->dvr_config_name[0] != '\0') {
if (a->aa_dvrcfgs == NULL)
a->aa_dvrcfgs = htsmsg_create_list();
htsmsg_add_str(a->aa_dvrcfgs, NULL, idnode_uuid_as_str(&ae->ae_dvr_config->dvr_id));
}
if(ae->ae_chtag && ae->ae_chtag->ct_name[0] != '\0') {
if (a->aa_chtags == NULL)
a->aa_chtags = htsmsg_create_list();
htsmsg_add_str(a->aa_chtags, NULL, ae->ae_chtag);
htsmsg_add_str(a->aa_chtags, NULL, idnode_uuid_as_str(&ae->ae_chtag->ct_id));
}
a->aa_rights |= ae->ae_rights;
@ -310,6 +323,14 @@ access_get(const char *username, const char *password, struct sockaddr *src)
access_t *a = calloc(1, sizeof(*a));
access_entry_t *ae;
if (username) {
a->aa_username = strdup(username);
a->aa_representative = strdup(username);
} else {
a->aa_representative = malloc(50);
tcp_get_ip_str((struct sockaddr*)src, a->aa_representative, 50);
}
if (access_noacl) {
a->aa_rights = ACCESS_FULL;
return a;
@ -546,12 +567,8 @@ access_entry_update_rights(access_entry_t *ae)
r |= ACCESS_ADVANCED_STREAMING;
if (ae->ae_dvr)
r |= ACCESS_RECORDER;
if (ae->ae_dvrallcfg)
r |= ACCESS_RECORDER_ALL;
if (ae->ae_webui)
r |= ACCESS_WEB_INTERFACE;
if (ae->ae_tag_only)
r |= ACCESS_TAG_ONLY;
if (ae->ae_admin)
r |= ACCESS_ADMIN;
ae->ae_rights = r;
@ -569,6 +586,7 @@ access_entry_create(const char *uuid, htsmsg_t *conf)
{
access_ipmask_t *ai;
access_entry_t *ae, *ae2;
const char *s;
lock_assert(&global_lock);
@ -585,6 +603,9 @@ access_entry_create(const char *uuid, htsmsg_t *conf)
if (conf) {
idnode_load(&ae->ae_id, conf);
/* note password has PO_NOSAVE, thus it must be set manually */
if ((s = htsmsg_get_str(conf, "password")) != NULL)
access_entry_class_password_set(ae, s);
access_entry_update_rights(ae);
TAILQ_FOREACH(ae2, &access_entries, ae_link)
if (ae->ae_index < ae2->ae_index)
@ -628,6 +649,11 @@ access_entry_destroy(access_entry_t *ae)
TAILQ_REMOVE(&access_entries, ae, ae_link);
idnode_unlink(&ae->ae_id);
if (ae->ae_dvr_config)
LIST_REMOVE(ae, ae_dvr_config_link);
if (ae->ae_chtag)
LIST_REMOVE(ae, ae_channel_tag_link);
while((ai = TAILQ_FIRST(&ae->ae_ipmasks)) != NULL)
{
TAILQ_REMOVE(&ae->ae_ipmasks, ai, ai_link);
@ -642,10 +668,42 @@ access_entry_destroy(access_entry_t *ae)
free(ae);
}
/*
*
*/
void
access_destroy_by_dvr_config(dvr_config_t *cfg, int delconf)
{
access_entry_t *ae;
while ((ae = LIST_FIRST(&cfg->dvr_accesses)) != NULL) {
LIST_REMOVE(ae, ae_dvr_config_link);
ae->ae_dvr_config = NULL;
if (delconf)
access_entry_save(ae);
}
}
/*
*
*/
void
access_destroy_by_channel_tag(channel_tag_t *ct, int delconf)
{
access_entry_t *ae;
while ((ae = LIST_FIRST(&ct->ct_accesses)) != NULL) {
LIST_REMOVE(ae, ae_channel_tag_link);
ae->ae_chtag = NULL;
if (delconf)
access_entry_save(ae);
}
}
/**
*
*/
static void
void
access_entry_save(access_entry_t *ae)
{
htsmsg_t *c = htsmsg_create_map();
@ -795,19 +853,72 @@ access_entry_class_password2_set(void *o, const void *v)
return 0;
}
static htsmsg_t *
access_entry_chtag_list ( void *o )
static int
access_entry_chtag_set(void *o, const void *v)
{
channel_tag_t *ct;
htsmsg_t *m = htsmsg_create_list();
TAILQ_FOREACH(ct, &channel_tags, ct_link)
htsmsg_add_str(m, NULL, ct->ct_name);
return m;
access_entry_t *ae = (access_entry_t *)o;
channel_tag_t *tag = v ? channel_tag_find_by_uuid(v) : NULL;
if (tag == NULL && ae->ae_chtag) {
LIST_REMOVE(ae, ae_channel_tag_link);
ae->ae_chtag = NULL;
return 1;
} else if (ae->ae_chtag != tag) {
if (ae->ae_chtag)
LIST_REMOVE(ae, ae_channel_tag_link);
ae->ae_chtag = tag;
LIST_INSERT_HEAD(&tag->ct_accesses, ae, ae_channel_tag_link);
return 1;
}
return 0;
}
static const void *
access_entry_chtag_get(void *o)
{
static const char *ret;
access_entry_t *ae = (access_entry_t *)o;
if (ae->ae_chtag)
ret = idnode_uuid_as_str(&ae->ae_chtag->ct_id);
else
ret = "";
return &ret;
}
static int
access_entry_dvr_config_set(void *o, const void *v)
{
access_entry_t *ae = (access_entry_t *)o;
dvr_config_t *cfg = v ? dvr_config_find_by_uuid(v) : NULL;
if (cfg == NULL && ae->ae_dvr_config) {
LIST_REMOVE(ae, ae_dvr_config_link);
ae->ae_dvr_config = NULL;
return 1;
} else if (ae->ae_dvr_config != cfg) {
if (ae->ae_dvr_config)
LIST_REMOVE(ae, ae_dvr_config_link);
ae->ae_dvr_config = cfg;
LIST_INSERT_HEAD(&cfg->dvr_accesses, ae, ae_dvr_config_link);
return 1;
}
return 0;
}
static const void *
access_entry_dvr_config_get(void *o)
{
static const char *ret;
access_entry_t *ae = (access_entry_t *)o;
if (ae->ae_dvr_config)
ret = idnode_uuid_as_str(&ae->ae_dvr_config->dvr_id);
else
ret = "";
return &ret;
}
const idclass_t access_entry_class = {
.ic_class = "access",
.ic_caption = "Access",
.ic_event = "access",
.ic_save = access_entry_class_save,
.ic_get_title = access_entry_class_get_title,
.ic_delete = access_entry_class_delete,
@ -875,10 +986,13 @@ const idclass_t access_entry_class = {
.off = offsetof(access_entry_t, ae_dvr),
},
{
.type = PT_BOOL,
.id = "dvrallcfg",
.name = "All Configs (VR)",
.off = offsetof(access_entry_t, ae_dvrallcfg),
.type = PT_STR,
.id = "dvr_config",
.name = "DVR Config Profile",
.set = access_entry_dvr_config_set,
.get = access_entry_dvr_config_get,
.list = dvr_entry_class_config_name_list,
.off = offsetof(access_entry_t, ae_dvr_config),
},
{
.type = PT_BOOL,
@ -892,12 +1006,6 @@ const idclass_t access_entry_class = {
.name = "Admin",
.off = offsetof(access_entry_t, ae_admin),
},
{
.type = PT_BOOL,
.id = "tag_only",
.name = "Username Channel Tag Match",
.off = offsetof(access_entry_t, ae_tag_only),
},
{
.type = PT_U32,
.id = "channel_min",
@ -915,7 +1023,9 @@ const idclass_t access_entry_class = {
.id = "channel_tag",
.name = "Channel Tag",
.off = offsetof(access_entry_t, ae_chtag),
.list = access_entry_chtag_list,
.set = access_entry_chtag_set,
.get = access_entry_chtag_get,
.list = channel_tag_class_get_list,
},
{
.type = PT_STR,
@ -956,7 +1066,7 @@ access_init(int createdefault, int noacl)
TAILQ_INIT(&access_tickets);
/* Load */
if ((c = hts_settings_load_r(1, "accesscontrol")) != NULL) {
if ((c = hts_settings_load("accesscontrol")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(m = htsmsg_field_get_map(f))) continue;
(void)access_entry_create(f->hmf_name, m);

View file

@ -22,6 +22,9 @@
#include "idnode.h"
#include "htsmsg.h"
struct dvr_config;
struct channel_tag;
typedef struct access_ipmask {
TAILQ_ENTRY(access_ipmask) ai_link;
@ -48,18 +51,25 @@ typedef struct access_entry {
char *ae_password;
char *ae_password2;
char *ae_comment;
int ae_index;
int ae_enabled;
int ae_streaming;
int ae_adv_streaming;
int ae_dvr;
int ae_dvrallcfg;
struct dvr_config *ae_dvr_config;
LIST_ENTRY(access_entry) ae_dvr_config_link;
int ae_webui;
int ae_admin;
int ae_tag_only;
uint32_t ae_chmin;
uint32_t ae_chmax;
char *ae_chtag;
struct channel_tag *ae_chtag;
LIST_ENTRY(access_entry) ae_channel_tag_link;
uint32_t ae_rights;
@ -82,7 +92,10 @@ typedef struct access_ticket {
} access_ticket_t;
typedef struct access {
char *aa_username;
char *aa_representative;
uint32_t aa_rights;
htsmsg_t *aa_dvrcfgs;
uint32_t aa_chmin;
uint32_t aa_chmax;
htsmsg_t *aa_chtags;
@ -94,14 +107,11 @@ typedef struct access {
#define ACCESS_ADVANCED_STREAMING (1<<1)
#define ACCESS_WEB_INTERFACE (1<<2)
#define ACCESS_RECORDER (1<<3)
#define ACCESS_RECORDER_ALL (1<<4)
#define ACCESS_TAG_ONLY (1<<5)
#define ACCESS_ADMIN (1<<6)
#define ACCESS_ADMIN (1<<4)
#define ACCESS_FULL \
(ACCESS_STREAMING | ACCESS_ADVANCED_STREAMING | \
ACCESS_WEB_INTERFACE | ACCESS_RECORDER | \
ACCESS_RECORDER_ALL | ACCESS_ADMIN)
ACCESS_WEB_INTERFACE | ACCESS_RECORDER | ACCESS_ADMIN)
/**
* Create a new ticket for the requested resource and generate a id for it
@ -129,6 +139,9 @@ void access_destroy(access_t *a);
int access_verify(const char *username, const char *password,
struct sockaddr *src, uint32_t mask);
static inline int access_verify2(access_t *a, uint32_t mask)
{ return (a->aa_rights & mask) == mask ? 0 : -1; }
/**
* Get the access structure
*/
@ -154,6 +167,20 @@ access_get_by_addr(struct sockaddr *src);
access_entry_t *
access_entry_create(const char *uuid, htsmsg_t *conf);
/**
*
*/
void
access_entry_save(access_entry_t *ae);
/**
*
*/
void
access_destroy_by_dvr_config(struct dvr_config *cfg, int delconf);
void
access_destroy_by_channel_tag(struct channel_tag *ct, int delconf);
/**
*
*/

View file

@ -61,7 +61,8 @@ api_register_all ( const api_hook_t *hooks )
}
int
api_exec ( const char *subsystem, htsmsg_t *args, htsmsg_t **resp )
api_exec ( access_t *perm, const char *subsystem,
htsmsg_t *args, htsmsg_t **resp )
{
api_hook_t h;
api_link_t *ah, skel;
@ -83,6 +84,9 @@ api_exec ( const char *subsystem, htsmsg_t *args, htsmsg_t **resp )
return ENOSYS; // TODO: is this really the right error code?
}
if (access_verify2(perm, ah->hook->ah_access))
return EPERM;
/* Extract method */
op = htsmsg_get_str(args, "method");
if (!op)
@ -90,12 +94,12 @@ api_exec ( const char *subsystem, htsmsg_t *args, htsmsg_t **resp )
// Note: this is not required (so no final validation)
/* Execute */
return ah->hook->ah_callback(ah->hook->ah_opaque, op, args, resp);
return ah->hook->ah_callback(perm, ah->hook->ah_opaque, op, args, resp);
}
static int
api_serverinfo
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
*resp = htsmsg_create_map();
htsmsg_add_str(*resp, "sw_version", tvheadend_version);
@ -128,6 +132,7 @@ void api_init ( void )
api_esfilter_init();
api_intlconv_init();
api_access_init();
api_dvr_init();
}
void api_done ( void )

View file

@ -23,15 +23,17 @@
#include "htsmsg.h"
#include "idnode.h"
#include "redblack.h"
#include "access.h"
#define TVH_API_VERSION 12
#define TVH_API_VERSION 14
/*
* Command hook
*/
typedef int (*api_callback_t)
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
( access_t *perm, void *opaque, const char *op,
htsmsg_t *args, htsmsg_t **resp );
typedef struct api_hook
{
@ -50,7 +52,8 @@ void api_register_all ( const api_hook_t *hooks );
/*
* Execute
*/
int api_exec ( const char *subsystem, htsmsg_t *args, htsmsg_t **resp );
int api_exec ( access_t *perm, const char *subsystem,
htsmsg_t *args, htsmsg_t **resp );
/*
* Initialise
@ -70,6 +73,7 @@ void api_imagecache_init ( void );
void api_esfilter_init ( void );
void api_intlconv_init ( void );
void api_access_init ( void );
void api_dvr_init ( void );
/*
* IDnode
@ -84,21 +88,24 @@ typedef struct api_idnode_grid_conf
} api_idnode_grid_conf_t;
typedef void (*api_idnode_grid_callback_t)
(idnode_set_t*, api_idnode_grid_conf_t*, htsmsg_t *args);
(access_t *perm, idnode_set_t*, api_idnode_grid_conf_t*, htsmsg_t *args);
typedef idnode_set_t *(*api_idnode_tree_callback_t)
(void);
(access_t *perm);
int api_idnode_grid
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
int api_idnode_class
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
int api_idnode_tree
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
int api_idnode_load_by_class
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
int api_idnode_handler
( access_t *perm, htsmsg_t *args, htsmsg_t **resp, void (*handler)(access_t *perm, idnode_t *in) );
/*
* Service mapper

View file

@ -23,7 +23,7 @@
static void
api_access_entry_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
access_entry_t *ae;
@ -33,15 +33,17 @@ api_access_entry_grid
static int
api_access_entry_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *conf;
access_entry_t *ae;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
pthread_mutex_lock(&global_lock);
access_entry_create(NULL, conf);
if ((ae = access_entry_create(NULL, conf)) != NULL)
access_entry_save(ae);
pthread_mutex_unlock(&global_lock);
return 0;

View file

@ -28,7 +28,7 @@
// TODO: this will need converting to an idnode system
static int
api_channel_list
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
channel_t *ch;
htsmsg_t *l, *e;
@ -50,7 +50,7 @@ api_channel_list
static void
api_channel_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
{
channel_t *ch;
@ -60,7 +60,7 @@ api_channel_grid
static int
api_channel_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *conf;
channel_t *ch;
@ -79,7 +79,7 @@ api_channel_create
static int
api_channel_tag_list
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
channel_tag_t *ct;
htsmsg_t *l, *e;
@ -98,7 +98,7 @@ api_channel_tag_list
static void
api_channel_tag_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
{
channel_tag_t *ct;
@ -108,7 +108,7 @@ api_channel_tag_grid
static int
api_channel_tag_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *conf;
channel_tag_t *ct;

358
src/api/api_dvr.c Normal file
View file

@ -0,0 +1,358 @@
/*
* API - DVR
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "dvr/dvr.h"
#include "epg.h"
#include "api.h"
static const char *
api_dvr_config_name( access_t *perm, const char *config_uuid )
{
dvr_config_t *cfg = NULL;
htsmsg_field_t *f;
const char *uuid;
lock_assert(&global_lock);
if (perm->aa_dvrcfgs == NULL)
return config_uuid; /* no change */
config_uuid = config_uuid ?: "";
HTSMSG_FOREACH(f, perm->aa_dvrcfgs) {
uuid = htsmsg_field_get_str(f) ?: "";
if (strcmp(uuid, config_uuid) == 0)
return config_uuid;
if (!cfg)
cfg = dvr_config_find_by_uuid(uuid);
}
if (!cfg && perm->aa_username)
tvhlog(LOG_INFO, "dvr", "User '%s' has no valid dvr config in ACL, using default...", perm->aa_username);
return cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL;
}
/*
*
*/
static void
api_dvr_config_grid
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_config_t *cfg;
LIST_FOREACH(cfg, &dvrconfigs, config_link)
if (!idnode_perm((idnode_t *)cfg, perm, NULL))
idnode_set_add(ins, (idnode_t*)cfg, &conf->filter);
}
static int
api_dvr_config_create
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
dvr_config_t *cfg;
htsmsg_t *conf;
const char *s;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
if (!(s = htsmsg_get_str(conf, "name")))
return EINVAL;
if (s[0] == '\0')
return EINVAL;
pthread_mutex_lock(&global_lock);
if ((cfg = dvr_config_create(NULL, NULL, conf)))
dvr_config_save(cfg);
pthread_mutex_unlock(&global_lock);
return 0;
}
static int is_dvr_entry_finished(dvr_entry_t *entry)
{
dvr_entry_sched_state_t state = entry->de_sched_state;
return state == DVR_COMPLETED && !entry->de_last_error && dvr_get_filesize(entry) != -1;
}
static int is_dvr_entry_upcoming(dvr_entry_t *entry)
{
dvr_entry_sched_state_t state = entry->de_sched_state;
return state == DVR_RECORDING || state == DVR_SCHEDULED;
}
static int is_dvr_entry_failed(dvr_entry_t *entry)
{
if (is_dvr_entry_finished(entry))
return 0;
if (is_dvr_entry_upcoming(entry))
return 0;
return 1;
}
static void
api_dvr_entry_grid
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link)
idnode_set_add(ins, (idnode_t*)de, &conf->filter);
}
static void
api_dvr_entry_grid_upcoming
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link)
if (is_dvr_entry_upcoming(de))
idnode_set_add(ins, (idnode_t*)de, &conf->filter);
}
static void
api_dvr_entry_grid_finished
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link)
if (is_dvr_entry_finished(de))
idnode_set_add(ins, (idnode_t*)de, &conf->filter);
}
static void
api_dvr_entry_grid_failed
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link)
if (is_dvr_entry_failed(de))
idnode_set_add(ins, (idnode_t*)de, &conf->filter);
}
static int
api_dvr_entry_create
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
dvr_entry_t *de;
htsmsg_t *conf;
const char *s1, *s2;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
pthread_mutex_lock(&global_lock);
s1 = htsmsg_get_str(conf, "config_name");
s2 = api_dvr_config_name(perm, s1);
if (strcmp(s1 ?: "", s2 ?: ""))
htsmsg_set_str(conf, "config_name", s2 ?: "");
if (perm->aa_representative)
htsmsg_set_str(conf, "creator", perm->aa_representative);
if ((de = dvr_entry_create(NULL, conf)))
dvr_entry_save(de);
pthread_mutex_unlock(&global_lock);
return 0;
}
static htsmsg_t *
api_dvr_entry_create_from_single(htsmsg_t *args)
{
htsmsg_t *entries, *m;
const char *s1, *s2;
if (!(s1 = htsmsg_get_str(args, "config_uuid")))
return NULL;
if (!(s2 = htsmsg_get_str(args, "event_id")))
return NULL;
entries = htsmsg_create_list();
m = htsmsg_create_map();
htsmsg_add_str(m, "config_uuid", s1);
htsmsg_add_str(m, "event_id", s2);
htsmsg_add_msg(entries, NULL, m);
return entries;
}
static int
api_dvr_entry_create_by_event
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
dvr_entry_t *de;
const char *config_uuid;
epg_broadcast_t *e;
htsmsg_t *entries, *entries2 = NULL, *m;
htsmsg_field_t *f;
const char *s;
int count = 0;
if (!(entries = htsmsg_get_list(args, "entries"))) {
entries = entries2 = api_dvr_entry_create_from_single(args);
if (!entries)
return EINVAL;
}
HTSMSG_FOREACH(f, entries) {
if (!(m = htsmsg_get_map_by_field(f))) continue;
if (!(config_uuid = htsmsg_get_str(m, "config_uuid")))
continue;
if (!(s = htsmsg_get_str(m, "event_id")))
continue;
pthread_mutex_lock(&global_lock);
if ((e = epg_broadcast_find_by_id(atoi(s), NULL))) {
de = dvr_entry_create_by_event(api_dvr_config_name(perm, config_uuid),
e, 0, 0, perm->aa_representative,
NULL, DVR_PRIO_NORMAL);
if (de)
dvr_entry_save(de);
}
pthread_mutex_unlock(&global_lock);
count++;
}
htsmsg_destroy(entries2);
return !count ? EINVAL : 0;
}
static void
api_dvr_cancel(access_t *perm, idnode_t *self)
{
dvr_entry_cancel((dvr_entry_t *)self);
}
static int
api_dvr_entry_cancel
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(perm, args, resp, api_dvr_cancel);
}
static void
api_dvr_autorec_grid
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
dvr_autorec_entry_t *dae;
TAILQ_FOREACH(dae, &autorec_entries, dae_link)
idnode_set_add(ins, (idnode_t*)dae, &conf->filter);
}
static int
api_dvr_autorec_create
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *conf;
dvr_autorec_entry_t *dae;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
if (perm->aa_representative)
htsmsg_set_str(conf, "creator", perm->aa_representative);
pthread_mutex_lock(&global_lock);
dae = dvr_autorec_create(NULL, conf);
if (dae) {
dvr_autorec_save(dae);
dvr_autorec_changed(dae, 1);
}
pthread_mutex_unlock(&global_lock);
return 0;
}
static int
api_dvr_autorec_create_by_series
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
dvr_autorec_entry_t *dae;
epg_broadcast_t *e;
htsmsg_t *entries, *entries2 = NULL, *m;
htsmsg_field_t *f;
const char *config_uuid, *s;
int count = 0;
if (!(entries = htsmsg_get_list(args, "entries"))) {
entries = entries2 = api_dvr_entry_create_from_single(args);
if (!entries)
return EINVAL;
}
HTSMSG_FOREACH(f, entries) {
if (!(m = htsmsg_get_map_by_field(f))) continue;
if (!(config_uuid = htsmsg_get_str(m, "config_uuid")))
continue;
if (!(s = htsmsg_get_str(m, "event_id")))
continue;
pthread_mutex_lock(&global_lock);
if ((e = epg_broadcast_find_by_id(atoi(s), NULL))) {
dae = dvr_autorec_add_series_link(api_dvr_config_name(perm, config_uuid),
e, perm->aa_representative,
"Created from EPG query");
if (dae) {
dvr_autorec_save(dae);
dvr_autorec_changed(dae, 1);
}
}
pthread_mutex_unlock(&global_lock);
count++;
}
htsmsg_destroy(entries2);
return !count ? EINVAL : 0;
}
void api_dvr_init ( void )
{
static api_hook_t ah[] = {
{ "dvr/config/class", ACCESS_RECORDER, api_idnode_class, (void*)&dvr_config_class },
{ "dvr/config/grid", ACCESS_RECORDER, api_idnode_grid, api_dvr_config_grid },
{ "dvr/config/create", ACCESS_ADMIN, api_dvr_config_create, NULL },
{ "dvr/entry/class", ACCESS_RECORDER, api_idnode_class, (void*)&dvr_entry_class },
{ "dvr/entry/grid", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid },
{ "dvr/entry/grid_upcoming", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_upcoming },
{ "dvr/entry/grid_finished", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_finished },
{ "dvr/entry/grid_failed", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_failed },
{ "dvr/entry/create", ACCESS_RECORDER, api_dvr_entry_create, NULL },
{ "dvr/entry/create_by_event", ACCESS_RECORDER, api_dvr_entry_create_by_event, NULL },
{ "dvr/entry/cancel", ACCESS_RECORDER, api_dvr_entry_cancel, NULL },
{ "dvr/autorec/class", ACCESS_RECORDER, api_idnode_class, (void*)&dvr_autorec_entry_class },
{ "dvr/autorec/grid", ACCESS_RECORDER, api_idnode_grid, api_dvr_autorec_grid },
{ "dvr/autorec/create", ACCESS_RECORDER, api_dvr_autorec_create, NULL },
{ "dvr/autorec/create_by_series", ACCESS_RECORDER, api_dvr_autorec_create_by_series, NULL },
{ NULL },
};
api_register_all(ah);
}

View file

@ -112,7 +112,7 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang )
/* Recording */
if ((de = dvr_entry_find_by_event(eb)))
htsmsg_add_u32(m, "dvrId", de->de_id);
htsmsg_add_str(m, "dvrId", idnode_uuid_as_str(&de->de_id));
/* Next event */
if ((eb = epg_broadcast_get_next(eb)))
@ -123,7 +123,7 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang )
static int
api_epg_grid
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int i;
epg_query_result_t eqr;
@ -176,10 +176,25 @@ api_epg_grid
return 0;
}
static int
api_epg_content_type_list(access_t *perm, void *opaque, const char *op,
htsmsg_t *args, htsmsg_t **resp)
{
htsmsg_t *array;
*resp = htsmsg_create_map();
array = epg_genres_list_all(1, 0);
htsmsg_add_msg(*resp, "entries", array);
return 0;
}
void api_epg_init ( void )
{
static api_hook_t ah[] = {
{ "epg/grid", ACCESS_ANONYMOUS, api_epg_grid, NULL },
{ "epg/data/grid", ACCESS_ANONYMOUS, api_epg_grid, NULL },
{ "epg/content_type/list", ACCESS_ANONYMOUS, api_epg_content_type_list, NULL },
{ NULL },
};

View file

@ -24,7 +24,7 @@
static int
api_epggrab_channel_list
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *m;
pthread_mutex_lock(&global_lock);

View file

@ -25,7 +25,7 @@
static void
api_esfilter_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args,
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args,
esfilter_class_t cls )
{
esfilter_t *esf;
@ -37,7 +37,7 @@ api_esfilter_grid
static int
api_esfilter_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp,
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp,
esfilter_class_t cls )
{
htsmsg_t *conf;
@ -54,11 +54,11 @@ api_esfilter_create
#define ESFILTER(func, t) \
static void api_esfilter_grid_##func \
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \
{ return api_esfilter_grid(ins, conf, args, (t)); } \
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \
{ return api_esfilter_grid(perm, ins, conf, args, (t)); } \
static int api_esfilter_create_##func \
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \
{ return api_esfilter_create(opaque, op, args, resp, (t)); }
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \
{ return api_esfilter_create(perm, opaque, op, args, resp, (t)); }
ESFILTER(video, ESF_CLASS_VIDEO);
ESFILTER(audio, ESF_CLASS_AUDIO);

View file

@ -23,6 +23,30 @@
#include "htsmsg.h"
#include "api.h"
static htsmsg_t *
api_idnode_flist_conf( htsmsg_t *args, const char *name )
{
htsmsg_t *m = NULL;
const char *s = htsmsg_get_str(args, name);
char *r, *saveptr;
if (s && s[0] != '\0') {
s = r = strdup(s);
r = strtok_r(r, ",;:", &saveptr);
while (r) {
while (*r != '\0' && *r <= ' ')
r++;
if (*r != '\0') {
if (m == NULL)
m = htsmsg_create_map();
htsmsg_add_bool(m, r, 1);
}
r = strtok_r(NULL, ",;:", &saveptr);
}
free((char *)s);
}
return m;
}
static struct strtab filtcmptab[] = {
{ "gt", IC_GT },
{ "lt", IC_LT },
@ -33,7 +57,7 @@ static void
api_idnode_grid_conf
( htsmsg_t *args, api_idnode_grid_conf_t *conf )
{
htsmsg_field_t *f;
htsmsg_field_t *f, *f2;
htsmsg_t *filter, *e;
const char *str;
@ -60,11 +84,20 @@ api_idnode_grid_conf
if ((v = htsmsg_get_str(e, "value")))
idnode_filter_add_str(&conf->filter, k, v, IC_RE);
} else if (!strcmp(t, "numeric")) {
uint32_t v;
if (!htsmsg_get_u32(e, "value", &v)) {
int t = str2val(htsmsg_get_str(e, "comparison") ?: "",
filtcmptab);
idnode_filter_add_num(&conf->filter, k, v, t == -1 ? IC_EQ : t);
f2 = htsmsg_field_find(e, "value");
if (f2) {
int t = str2val(htsmsg_get_str(e, "comparison") ?: "", filtcmptab);
if (f2->hmf_type == HMF_DBL) {
double dbl;
if (!htsmsg_field_get_dbl(f2, &dbl))
idnode_filter_add_dbl(&conf->filter, k, dbl, t == -1 ? IC_EQ : t);
} else {
int64_t v;
int64_t intsplit = 0;
htsmsg_get_s64(e, "intsplit", &intsplit);
if (!htsmsg_field_get_s64(f2, &v))
idnode_filter_add_num(&conf->filter, k, v, t == -1 ? IC_EQ : t, intsplit);
}
}
} else if (!strcmp(t, "boolean")) {
uint32_t v;
@ -87,10 +120,11 @@ api_idnode_grid_conf
int
api_idnode_grid
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int i;
htsmsg_t *list, *e;
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
api_idnode_grid_conf_t conf = { 0 };
idnode_set_t ins = { 0 };
api_idnode_grid_callback_t cb = opaque;
@ -100,7 +134,7 @@ api_idnode_grid
/* Create list */
pthread_mutex_lock(&global_lock);
cb(&ins, &conf, args);
cb(perm, &ins, &conf, args);
/* Sort */
if (conf.sort.key)
@ -111,7 +145,7 @@ api_idnode_grid
for (i = conf.start; i < ins.is_count && conf.limit != 0; i++) {
e = htsmsg_create_map();
htsmsg_add_str(e, "uuid", idnode_uuid_as_str(ins.is_array[i]));
idnode_read0(ins.is_array[i], e, 0);
idnode_read0(ins.is_array[i], e, flist, 0);
htsmsg_add_msg(list, NULL, e);
if (conf.limit > 0) conf.limit--;
}
@ -126,13 +160,14 @@ api_idnode_grid
/* Cleanup */
free(ins.is_array);
idnode_filter_clear(&conf.filter);
htsmsg_destroy(flist);
return 0;
}
int
api_idnode_load_by_class
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int i, _enum;
const idclass_t *idc;
@ -154,6 +189,9 @@ api_idnode_load_by_class
for (i = 0; i < is->is_count; i++) {
in = is->is_array[i];
if (idnode_perm(in, perm, NULL))
continue;
/* Name/UUID only */
if (_enum) {
e = htsmsg_create_map();
@ -161,8 +199,11 @@ api_idnode_load_by_class
htsmsg_add_str(e, "val", idnode_get_title(in));
/* Full record */
} else
e = idnode_serialize(in);
} else {
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
e = idnode_serialize0(in, flist, 0);
htsmsg_destroy(flist);
}
if (e)
htsmsg_add_msg(l, NULL, e);
@ -180,11 +221,12 @@ api_idnode_load_by_class
static int
api_idnode_load
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err = 0;
int err = 0, meta = 0, count = 0;
idnode_t *in;
htsmsg_t *uuids, *l = NULL;
htsmsg_t *uuids, *l = NULL, *m;
htsmsg_t *flist;
htsmsg_field_t *f;
const char *uuid, *class;
@ -197,7 +239,7 @@ api_idnode_load
if (!idc)
return EINVAL;
// TODO: bit naff that 2 locks are required here
return api_idnode_load_by_class((void*)idc, NULL, args, resp);
return api_idnode_load_by_class(perm, (void*)idc, NULL, args, resp);
}
/* UUIDs */
@ -206,6 +248,9 @@ api_idnode_load
if (!(uuids = htsmsg_field_get_list(f)))
if (!(uuid = htsmsg_field_get_str(f)))
return EINVAL;
htsmsg_get_s32(args, "meta", &meta);
flist = api_idnode_flist_conf(args, "list");
pthread_mutex_lock(&global_lock);
@ -215,16 +260,34 @@ api_idnode_load
HTSMSG_FOREACH(f, uuids) {
if (!(uuid = htsmsg_field_get_str(f))) continue;
if (!(in = idnode_find(uuid, NULL))) continue;
htsmsg_add_msg(l, NULL, idnode_serialize(in));
if (idnode_perm(in, perm, NULL)) {
err = EPERM;
continue;
}
m = idnode_serialize0(in, flist, 0);
if (meta > 0)
htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0));
htsmsg_add_msg(l, NULL, m);
count++;
}
if (count)
err = 0;
/* Single */
} else {
if (!(in = idnode_find(uuid, NULL)))
err = ENOENT;
else {
l = htsmsg_create_list();
htsmsg_add_msg(l, NULL, idnode_serialize(in));
if (idnode_perm(in, perm, NULL)) {
err = EPERM;
} else {
l = htsmsg_create_list();
m = idnode_serialize0(in, flist, 0);
if (meta > 0)
htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0));
htsmsg_add_msg(l, NULL, m);
}
}
}
@ -235,18 +298,21 @@ api_idnode_load
pthread_mutex_unlock(&global_lock);
htsmsg_destroy(flist);
return err;
}
static int
api_idnode_save
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err = EINVAL;
idnode_t *in;
htsmsg_t *msg, *conf;
htsmsg_field_t *f;
const char *uuid;
int count = 0;
if (!(f = htsmsg_field_find(args, "node")))
return EINVAL;
@ -262,6 +328,10 @@ api_idnode_save
goto exit;
if (!(in = idnode_find(uuid, NULL)))
goto exit;
if (idnode_perm(in, perm, msg)) {
err = EPERM;
goto exit;
}
idnode_update(in, msg);
err = 0;
@ -274,9 +344,15 @@ api_idnode_save
continue;
if (!(in = idnode_find(uuid, NULL)))
continue;
if (idnode_perm(in, perm, conf)) {
err = EPERM;
continue;
}
count++;
idnode_update(in, conf);
}
err = 0;
if (count)
err = 0;
}
// TODO: return updated UUIDs?
@ -289,7 +365,7 @@ exit:
int
api_idnode_tree
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
const char *uuid;
const char *root = NULL;
@ -329,7 +405,7 @@ api_idnode_tree
/* Children */
} else {
idnode_set_t *v = node ? idnode_get_childs(node) : rootfn();
idnode_set_t *v = node ? idnode_get_childs(node) : rootfn(perm);
if (v) {
int i;
idnode_set_sort_by_title(v);
@ -348,11 +424,12 @@ api_idnode_tree
int
api_idnode_class
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err = EINVAL;
const char *name;
const idclass_t *idc;
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
pthread_mutex_lock(&global_lock);
@ -368,17 +445,20 @@ api_idnode_class
}
err = 0;
*resp = idclass_serialize(idc);
*resp = idclass_serialize0(idc, flist, 0);
exit:
pthread_mutex_unlock(&global_lock);
htsmsg_destroy(flist);
return err;
}
static int
int
api_idnode_handler
( htsmsg_t *args, htsmsg_t **resp, void (*handler)(idnode_t *in) )
( access_t *perm, htsmsg_t *args, htsmsg_t **resp,
void (*handler)(access_t *perm, idnode_t *in) )
{
int err = 0;
idnode_t *in;
@ -400,7 +480,7 @@ api_idnode_handler
HTSMSG_FOREACH(f, uuids) {
if (!(uuid = htsmsg_field_get_string(f))) continue;
if (!(in = idnode_find(uuid, NULL))) continue;
handler(in);
handler(perm, in);
}
/* Single */
@ -409,7 +489,7 @@ api_idnode_handler
if (!(in = idnode_find(uuid, NULL)))
err = ENOENT;
else
handler(in);
handler(perm, in);
}
pthread_mutex_unlock(&global_lock);
@ -417,25 +497,43 @@ api_idnode_handler
return err;
}
static void
api_idnode_delete_ (access_t *perm, idnode_t *in)
{
return idnode_delete(in);
}
static int
api_idnode_delete
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_delete);
return api_idnode_handler(perm, args, resp, api_idnode_delete_);
}
static void
api_idnode_moveup_ (access_t *perm, idnode_t *in)
{
return idnode_moveup(in);
}
static int
api_idnode_moveup
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_moveup);
return api_idnode_handler(perm, args, resp, api_idnode_moveup_);
}
static void
api_idnode_movedown_ (access_t *perm, idnode_t *in)
{
return idnode_movedown(in);
}
static int
api_idnode_movedown
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_movedown);
return api_idnode_handler(perm, args, resp, api_idnode_movedown_);
}
void api_idnode_init ( void )

View file

@ -27,7 +27,7 @@
static int
api_imagecache_load
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *l;
pthread_mutex_lock(&global_lock);
@ -41,7 +41,7 @@ api_imagecache_load
static int
api_imagecache_save
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
pthread_mutex_lock(&global_lock);
if (imagecache_set_config(args))

View file

@ -27,28 +27,22 @@
static int
api_intlconv_charset_enum
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
const char **chrst;
htsmsg_t *l, *e;
int _enum = htsmsg_get_bool_or_default(args, "enum", 0);
if (_enum) {
l = htsmsg_create_list();
chrst = intlconv_charsets;
while (*chrst) {
e = htsmsg_create_map();
htsmsg_add_str(e, "key", *chrst);
htsmsg_add_str(e, "val", *chrst);
htsmsg_add_msg(l, NULL, e);
chrst++;
}
*resp = htsmsg_create_map();
htsmsg_add_msg(*resp, "entries", l);
} else {
// TODO: support full listing v enum
l = htsmsg_create_list();
chrst = intlconv_charsets;
while (*chrst) {
e = htsmsg_create_map();
htsmsg_add_str(e, "key", *chrst);
htsmsg_add_str(e, "val", *chrst);
htsmsg_add_msg(l, NULL, e);
chrst++;
}
*resp = htsmsg_create_map();
htsmsg_add_msg(*resp, "entries", l);
return 0;
}

View file

@ -31,7 +31,7 @@
*/
static int
api_mpegts_input_network_list
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int i, err = EINVAL;
const char *uuid;
@ -77,7 +77,7 @@ exit:
*/
static void
api_mpegts_network_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
mpegts_network_t *mn;
@ -88,7 +88,7 @@ api_mpegts_network_grid
static int
api_mpegts_network_builders
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
mpegts_network_builder_t *mnb;
htsmsg_t *l, *e;
@ -108,7 +108,7 @@ api_mpegts_network_builders
static int
api_mpegts_network_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err;
const char *class;
@ -136,7 +136,7 @@ api_mpegts_network_create
static int
api_mpegts_network_muxclass
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err = EINVAL;
const idclass_t *idc;
@ -164,7 +164,7 @@ exit:
static int
api_mpegts_network_muxcreate
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err = EINVAL;
mpegts_network_t *mn;
@ -198,7 +198,7 @@ exit:
*/
static void
api_mpegts_mux_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
mpegts_network_t *mn;
mpegts_mux_t *mm;
@ -225,7 +225,7 @@ api_mpegts_mux_grid
*/
static void
api_mpegts_service_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
mpegts_network_t *mn;
mpegts_mux_t *mm;
@ -256,7 +256,7 @@ api_mpegts_service_grid
*/
static void
api_mpegts_mux_sched_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
{
mpegts_mux_sched_t *mms;
LIST_FOREACH(mms, &mpegts_mux_sched_all, mms_link)
@ -265,7 +265,7 @@ api_mpegts_mux_sched_grid
static int
api_mpegts_mux_sched_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int err;
htsmsg_t *conf;
@ -291,7 +291,7 @@ api_mpegts_mux_sched_create
#if ENABLE_MPEGTS_DVB
static int
api_dvb_scanfile_list
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
char buf[512];
const char *type = htsmsg_get_str(args, "type");

View file

@ -29,7 +29,7 @@
static int
api_mapper_start
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
service_mapper_conf_t conf = { 0 };
htsmsg_t *uuids;
@ -52,7 +52,7 @@ api_mapper_start
static int
api_mapper_stop
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
pthread_mutex_lock(&global_lock);
service_mapper_stop();
@ -78,7 +78,7 @@ api_mapper_status_msg ( void )
static int
api_mapper_status
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
pthread_mutex_lock(&global_lock);
*resp = api_mapper_status_msg();
@ -129,7 +129,7 @@ api_service_streams_get_one ( elementary_stream_t *es, int use_filter )
static int
api_service_streams
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
const char *uuid;
htsmsg_t *e, *st, *stf;

View file

@ -29,7 +29,7 @@
static int
api_status_inputs
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int c = 0;
htsmsg_t *l, *e;
@ -59,7 +59,7 @@ api_status_inputs
static int
api_status_subscriptions
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int c;
htsmsg_t *l, *e;
@ -82,7 +82,7 @@ api_status_subscriptions
static int
api_status_connections
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
pthread_mutex_lock(&global_lock);
*resp = tcp_server_connections();

View file

@ -163,19 +163,6 @@ channel_class_tags_set ( void *obj, const void *p )
return channel_set_tags_by_list(obj, (htsmsg_t*)p);
}
static htsmsg_t *
channel_class_tags_enum ( void *obj )
{
htsmsg_t *e, *m = htsmsg_create_map();
htsmsg_add_str(m, "type", "api");
htsmsg_add_str(m, "uri", "channeltag/list");
htsmsg_add_str(m, "event", "channeltag");
e = htsmsg_create_map();
htsmsg_add_bool(e, "enum", 1);
htsmsg_add_msg(m, "params", e);
return m;
}
static void
channel_class_icon_notify ( void *obj )
{
@ -221,7 +208,7 @@ channel_class_get_name ( void *p )
static const void *
channel_class_get_number ( void *p )
{
static int i;
static int64_t i;
i = channel_get_number(p);
return &i;
}
@ -288,6 +275,7 @@ channel_class_epggrab_list ( void *o )
const idclass_t channel_class = {
.ic_class = "channel",
.ic_caption = "Channel",
.ic_event = "channel",
.ic_save = channel_class_save,
.ic_get_title = channel_class_get_title,
.ic_delete = channel_class_delete,
@ -308,7 +296,8 @@ const idclass_t channel_class = {
.get = channel_class_get_name,
},
{
.type = PT_INT,
.type = PT_S64,
.intsplit = CHANNEL_SPLIT,
.id = "number",
.name = "Number",
.off = offsetof(channel_t, ch_number),
@ -369,7 +358,7 @@ const idclass_t channel_class = {
.name = "Tags",
.get = channel_class_tags_get,
.set = channel_class_tags_set,
.list = channel_class_tags_enum,
.list = channel_tag_class_get_list,
.rend = channel_class_tags_rend
},
{}
@ -430,7 +419,8 @@ channel_access(channel_t *ch, access_t *a, const char *username)
htsmsg_field_t *f;
HTSMSG_FOREACH(f, a->aa_chtags) {
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
if (!strcmp(htsmsg_field_get_str(f) ?: "", ctm->ctm_tag->ct_name))
if (!strcmp(htsmsg_field_get_str(f) ?: "",
idnode_uuid_as_str(&ctm->ctm_tag->ct_id)))
goto chtags_ok;
}
}
@ -438,17 +428,6 @@ channel_access(channel_t *ch, access_t *a, const char *username)
}
chtags_ok:
/* Channel tag <-> user name match */
if (ch && (a->aa_rights & ACCESS_TAG_ONLY) != 0) {
channel_tag_mapping_t *ctm;
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
if (!strcmp(username ?: "", ctm->ctm_tag->ct_name))
goto tagonly_ok;
}
return 0;
}
tagonly_ok:
return 1;
}
@ -529,7 +508,7 @@ channel_get_name ( channel_t *ch )
return blank;
}
int
int64_t
channel_get_number ( channel_t *ch )
{
int n;
@ -620,7 +599,7 @@ channel_delete ( channel_t *ch, int delconf )
/* Settings */
if (delconf)
hts_settings_remove("channel/%s", idnode_uuid_as_str(&ch->ch_id));
hts_settings_remove("channel/config/%s", idnode_uuid_as_str(&ch->ch_id));
/* Free memory */
RB_REMOVE(&channels, ch, ch_link);
@ -638,7 +617,7 @@ channel_save ( channel_t *ch )
{
htsmsg_t *c = htsmsg_create_map();
idnode_save(&ch->ch_id, c);
hts_settings_save(c, "channel/%s", idnode_uuid_as_str(&ch->ch_id));
hts_settings_save(c, "channel/config/%s", idnode_uuid_as_str(&ch->ch_id));
htsmsg_destroy(c);
}
@ -656,7 +635,7 @@ channel_init ( void )
channel_tag_init();
/* Channels */
if (!(c = hts_settings_load_r(1, "channel")))
if (!(c = hts_settings_load("channel/config")))
return;
HTSMSG_FOREACH(f, c) {
@ -761,6 +740,9 @@ channel_tag_create(const char *uuid, htsmsg_t *conf)
channel_tag_t *ct;
ct = calloc(1, sizeof(channel_tag_t));
LIST_INIT(&ct->ct_ctms);
LIST_INIT(&ct->ct_autorecs);
LIST_INIT(&ct->ct_accesses);
if (idnode_insert(&ct->ct_id, uuid, &channel_tag_class, IDNODE_SHORT_UUID)) {
if (uuid)
@ -798,7 +780,7 @@ channel_tag_destroy(channel_tag_t *ct, int delconf)
channel_tag_mapping_destroy(ctm, CTM_DESTROY_UPDATE_CHANNEL);
channel_save(ch);
}
hts_settings_remove("channeltags/%s", idnode_uuid_as_str(&ct->ct_id));
hts_settings_remove("channel/tag/%s", idnode_uuid_as_str(&ct->ct_id));
}
if(ct->ct_enabled && !ct->ct_internal)
@ -807,6 +789,9 @@ channel_tag_destroy(channel_tag_t *ct, int delconf)
TAILQ_REMOVE(&channel_tags, ct, ct_link);
idnode_unlink(&ct->ct_id);
autorec_destroy_by_channel_tag(ct, delconf);
access_destroy_by_channel_tag(ct, delconf);
free(ct->ct_name);
free(ct->ct_comment);
free(ct->ct_icon);
@ -821,7 +806,7 @@ channel_tag_save(channel_tag_t *ct)
{
htsmsg_t *c = htsmsg_create_map();
idnode_save(&ct->ct_id, c);
hts_settings_save(c, "channeltags/%s", idnode_uuid_as_str(&ct->ct_id));
hts_settings_save(c, "channel/tag/%s", idnode_uuid_as_str(&ct->ct_id));
htsmsg_destroy(c);
}
@ -849,9 +834,21 @@ channel_tag_class_get_title (idnode_t *self)
return ct->ct_name ?: "";
}
/* exported for others */
htsmsg_t *
channel_tag_class_get_list(void *o)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "type", "api");
htsmsg_add_str(m, "uri", "channeltag/list");
htsmsg_add_str(m, "event", "channeltag");
return m;
}
const idclass_t channel_tag_class = {
.ic_class = "channeltag",
.ic_caption = "Channel Tag",
.ic_event = "channeltag",
.ic_save = channel_tag_class_save,
.ic_get_title = channel_tag_class_get_title,
.ic_delete = channel_tag_class_delete,
@ -946,7 +943,7 @@ channel_tag_init ( void )
htsmsg_field_t *f;
TAILQ_INIT(&channel_tags);
if ((c = hts_settings_load_r(1, "channeltags")) != NULL) {
if ((c = hts_settings_load("channel/tag")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(m = htsmsg_field_get_map(f))) continue;
(void)channel_tag_create(f->hmf_name, m);

View file

@ -47,10 +47,10 @@ typedef struct channel
int ch_zombie;
/* Channel info */
char *ch_name; // Note: do not access directly!
int ch_number;
char *ch_icon;
struct channel_tag_mapping_list ch_ctms;
char *ch_name; // Note: do not access directly!
int64_t ch_number;
char *ch_icon;
struct channel_tag_mapping_list ch_ctms;
/* Service/subscriptions */
LIST_HEAD(, channel_service_mapping) ch_services;
@ -95,6 +95,8 @@ typedef struct channel_tag {
struct dvr_autorec_entry_list ct_autorecs;
struct access_entry_list ct_accesses;
int ct_htsp_id;
} channel_tag_t;
@ -164,6 +166,8 @@ static inline channel_tag_t *channel_tag_find_by_uuid(const char *uuid)
void channel_tag_save(channel_tag_t *ct);
htsmsg_t * channel_tag_class_get_list(void *o);
int channel_access(channel_t *ch, struct access *a, const char *username);
int channel_tag_map(channel_t *ch, channel_tag_t *ct);
@ -173,7 +177,12 @@ void channel_save(channel_t *ch);
const char *channel_get_name ( channel_t *ch );
int channel_set_name ( channel_t *ch, const char *s );
int channel_get_number ( channel_t *ch );
#define CHANNEL_SPLIT 1000000
static inline uint32_t channel_get_major ( int64_t chnum ) { return chnum / CHANNEL_SPLIT; }
static inline uint32_t channel_get_minor ( int64_t chnum ) { return chnum % CHANNEL_SPLIT; }
int64_t channel_get_number ( channel_t *ch );
const char *channel_get_icon ( channel_t *ch );
int channel_set_icon ( channel_t *ch, const char *icon );

View file

@ -16,13 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <sys/stat.h>
#include "tvheadend.h"
#include "settings.h"
#include "config.h"
#include "uuid.h"
#include <string.h>
#include <sys/stat.h>
#include "htsbuf.h"
#include "spawn.h"
/* *************************************************************************
* Global data
@ -620,7 +622,7 @@ config_migrate_v6 ( void )
* v6 -> v7 : acesscontrol changes
*/
static void
config_migrate_simple ( const char *dir, htsmsg_t **orig,
config_migrate_simple ( const char *dir, htsmsg_t *list,
void (*modify)(htsmsg_t *record,
uint32_t id,
const char *uuid,
@ -632,25 +634,31 @@ config_migrate_simple ( const char *dir, htsmsg_t **orig,
tvh_uuid_t u;
uint32_t index = 1, id;
if (!(c = hts_settings_load_r(1, dir)))
if (!(c = hts_settings_load(dir)))
return;
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
uuid_init_hex(&u, NULL);
if (htsmsg_get_u32(e, "id", &id))
id = 0;
else if (list) {
htsmsg_t *m = htsmsg_create_map();
char buf[16];
snprintf(buf, sizeof(buf), "%d", id);
htsmsg_add_str(m, "id", buf);
htsmsg_add_str(m, "uuid", u.hex);
htsmsg_add_msg(list, NULL, m);
}
htsmsg_delete_field(e, "id");
htsmsg_add_u32(e, "index", index++);
uuid_init_hex(&u, NULL);
modify(e, id, u.hex, aux);
if (modify)
modify(e, id, u.hex, aux);
hts_settings_save(e, "%s/%s", dir, u.hex);
hts_settings_remove("%s/%s", dir, f->hmf_name);
}
if (orig)
*orig = c;
else
htsmsg_destroy(c);
htsmsg_destroy(c);
}
static void
@ -731,6 +739,313 @@ config_migrate_v8 ( void )
htsmsg_destroy(ch);
}
static void
config_modify_autorec( htsmsg_t *c, uint32_t id, const char *uuid, void *aux )
{
uint32_t u32;
htsmsg_delete_field(c, "index");
if (!htsmsg_get_u32(c, "approx_time", &u32)) {
htsmsg_delete_field(c, "approx_time");
if (u32 != 0)
htsmsg_add_u32(c, "start", u32);
else
htsmsg_add_str(c, "start", "");
}
if (!htsmsg_get_u32(c, "contenttype", &u32)) {
htsmsg_delete_field(c, "contenttype");
htsmsg_add_u32(c, "content_type", u32 / 16);
}
}
static void
config_modify_dvr_log( htsmsg_t *c, uint32_t id, const char *uuid, void *aux )
{
htsmsg_t *list = aux;
const char *chname = htsmsg_get_str(c, "channelname");
const char *chuuid = htsmsg_get_str(c, "channel");
htsmsg_t *e;
htsmsg_field_t *f;
tvh_uuid_t uuid0;
const char *s1;
uint32_t u32;
htsmsg_delete_field(c, "index");
if (chname == NULL || (chuuid != NULL && uuid_init_bin(&uuid0, chuuid))) {
chname = strdup(chuuid);
htsmsg_delete_field(c, "channelname");
htsmsg_delete_field(c, "channel");
htsmsg_add_str(c, "channelname", chname);
free((char *)chname);
if (!htsmsg_get_u32(c, "contenttype", &u32)) {
htsmsg_delete_field(c, "contenttype");
htsmsg_add_u32(c, "content_type", u32 / 16);
}
}
if ((s1 = htsmsg_get_str(c, "autorec")) != NULL) {
s1 = strdup(s1);
htsmsg_delete_field(c, "autorec");
HTSMSG_FOREACH(f, list) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (strcmp(s1, htsmsg_get_str(e, "id")) == 0) {
htsmsg_add_str(c, "autorec", htsmsg_get_str(e, "uuid"));
break;
}
}
}
}
static void
config_migrate_v9 ( void )
{
htsmsg_t *list = htsmsg_create_list();
htsmsg_t *c, *e;
htsmsg_field_t *f;
tvh_uuid_t u;
config_migrate_simple("autorec", list, config_modify_autorec, NULL);
config_migrate_simple("dvr/log", NULL, config_modify_dvr_log, list);
htsmsg_destroy(list);
if ((c = hts_settings_load("dvr")) != NULL) {
/* step 1: only "config" */
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (strcmp(f->hmf_name, "config")) continue;
htsmsg_add_str(e, "name", f->hmf_name + 6);
uuid_init_hex(&u, NULL);
hts_settings_remove("dvr/%s", f->hmf_name);
hts_settings_save(e, "dvr/config/%s", u.hex);
}
/* step 2: reset (without "config") */
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (strcmp(f->hmf_name, "config") == 0) continue;
if (strncmp(f->hmf_name, "config", 6)) continue;
htsmsg_add_str(e, "name", f->hmf_name + 6);
uuid_init_hex(&u, NULL);
hts_settings_remove("dvr/%s", f->hmf_name);
hts_settings_save(e, "dvr/config/%s", u.hex);
}
htsmsg_destroy(c);
}
if ((c = hts_settings_load("autorec")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
hts_settings_remove("autorec/%s", f->hmf_name);
hts_settings_save(e, "dvr/autorec/%s", f->hmf_name);
}
}
}
static void
config_migrate_move ( const char *dir,
const char *newdir )
{
htsmsg_t *c, *e;
htsmsg_field_t *f;
if (!(c = hts_settings_load(dir)))
return;
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
hts_settings_save(e, "%s/%s", newdir, f->hmf_name);
hts_settings_remove("%s/%s", dir, f->hmf_name);
}
htsmsg_destroy(c);
}
static void
config_migrate_v10 ( void )
{
config_migrate_move("channel", "channel/config");
config_migrate_move("channeltags", "channel/tag");
}
static const char *
config_find_uuid( htsmsg_t *map, const char *name, const char *value )
{
htsmsg_t *e;
htsmsg_field_t *f;
const char *s;
HTSMSG_FOREACH(f, map) {
if (!(e = htsmsg_field_get_map(f))) continue;
if ((s = htsmsg_get_str(e, name)) != NULL) {
if (!strcmp(s, value))
return f->hmf_name;
}
}
return NULL;
}
static void
config_modify_acl_dvallcfg( htsmsg_t *c, htsmsg_t *dvr_config )
{
uint32_t a;
const char *username, *uuid;
username = htsmsg_get_str(c, "username");
if (!htsmsg_get_u32(c, "dvallcfg", &a))
if (a == 0) {
uuid = username ? config_find_uuid(dvr_config, "name", username) : NULL;
if (uuid)
htsmsg_add_str(c, "dvr_config", uuid);
}
htsmsg_delete_field(c, "dvallcfg");
}
static void
config_modify_acl_tag_only( htsmsg_t *c, htsmsg_t *channel_tag )
{
uint32_t a;
const char *username, *tag, *uuid;
username = htsmsg_get_str(c, "username");
tag = htsmsg_get_str(c, "channel_tag");
if (!tag || tag[0] == '\0')
tag = NULL;
if (tag == NULL && !htsmsg_get_u32(c, "tag_only", &a)) {
if (a) {
uuid = username ? config_find_uuid(channel_tag, "name", username) : NULL;
if (uuid)
htsmsg_add_str(c, "channel_tag", uuid);
}
} else if (tag) {
uuid = config_find_uuid(channel_tag, "name", tag);
if (uuid) {
htsmsg_delete_field(c, "channel_tag");
htsmsg_add_str(c, "channel_tag", uuid);
}
}
htsmsg_delete_field(c, "tag_only");
}
static void
config_modify_dvr_config_name( htsmsg_t *c, htsmsg_t *dvr_config )
{
const char *config_name, *uuid;
config_name = htsmsg_get_str(c, "config_name");
uuid = config_name ? config_find_uuid(dvr_config, "name", config_name) : NULL;
htsmsg_delete_field(c, "config_name");
htsmsg_add_str(c, "config_name", uuid ?: "");
}
static void
config_migrate_v11 ( void )
{
htsmsg_t *dvr_config;
htsmsg_t *channel_tag;
htsmsg_t *c, *e;
htsmsg_field_t *f;
dvr_config = hts_settings_load("dvr/config");
channel_tag = hts_settings_load("channel/tag");
if ((c = hts_settings_load("accesscontrol")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
config_modify_acl_dvallcfg(e, dvr_config);
config_modify_acl_tag_only(e, channel_tag);
}
htsmsg_destroy(c);
}
if ((c = hts_settings_load("dvr/log")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
config_modify_dvr_config_name(e, dvr_config);
}
htsmsg_destroy(c);
}
htsmsg_destroy(channel_tag);
htsmsg_destroy(dvr_config);
}
/*
* Perform backup
*/
static void
dobackup(const char *oldver)
{
char outfile[PATH_MAX], cwd[PATH_MAX];
const char *argv[] = {
"/usr/bin/tar", "cjf", outfile, "--exclude", "backup", ".", NULL
};
const char *root = hts_settings_get_root();
char errtxt[128];
const char **arg;
int code;
tvhinfo("config", "backup: migrating config from %s (running %s)",
oldver, tvheadend_version);
if (getcwd(cwd, sizeof(cwd)) == NULL) {
tvherror("config", "unable to get the current working directory");
goto fatal;
}
if (!access("/bin/tar", X_OK))
argv[0] = "/bin/tar";
else if (!access("/usr/bin/tar", X_OK))
argv[0] = "/usr/bin/tar";
else if (!access("/usr/local/bin/tar", X_OK))
argv[0] = "/usr/local/bin/tar";
else {
tvherror("config", "unable to find tar program");
goto fatal;
}
snprintf(outfile, sizeof(outfile), "%s/backup", root);
if (makedirs(outfile, 0700))
goto fatal;
if (chdir(root)) {
tvherror("config", "unable to find directory '%s'", root);
goto fatal;
}
snprintf(outfile, sizeof(outfile), "%s/backup/%s.tar.bz2",
root, oldver);
tvhinfo("config", "backup: running, output file %s", outfile);
spawnv(argv[0], (void *)argv);
while ((code = spawn_reap(errtxt, sizeof(errtxt))) == -EAGAIN)
usleep(20000);
if (code) {
htsbuf_queue_t q;
char *s;
htsbuf_queue_init(&q, 0);
for (arg = argv; *arg; arg++) {
htsbuf_append(&q, *arg, strlen(*arg));
if (arg[1])
htsbuf_append(&q, " ", 1);
}
s = htsbuf_to_string(&q);
tvherror("config", "command '%s' returned error code %d", s, code);
tvherror("config", "executed in directory '%s'", root);
free(s);
htsbuf_queue_flush(&q);
goto fatal;
}
if (chdir(cwd)) {
tvherror("config", "unable to change directory to '%s'", cwd);
goto fatal;
}
return;
fatal:
tvherror("config", "backup: fatal error");
exit(EXIT_FAILURE);
}
/*
* Migration table
*/
@ -743,18 +1058,28 @@ static const config_migrate_t config_migrate_table[] = {
config_migrate_v6,
config_migrate_v7,
config_migrate_v8,
config_migrate_v9,
config_migrate_v10,
config_migrate_v11
};
/*
* Perform migrations (if required)
*/
static void
config_migrate ( void )
static int
config_migrate ( int backup )
{
uint32_t v;
const char *s;
/* Get the current version */
v = htsmsg_get_u32_or_default(config, "version", 0);
s = htsmsg_get_str(config, "fullversion") ?: "unknown";
if (backup && strcmp(s, tvheadend_version))
dobackup(s);
else
backup = 0;
/* Attempt to auto-detect versions prior to v2 */
if (!v) {
@ -765,8 +1090,11 @@ config_migrate ( void )
}
/* No changes required */
if (v == ARRAY_SIZE(config_migrate_table))
return;
if (v == ARRAY_SIZE(config_migrate_table)) {
if (backup)
goto update;
return 0;
}
/* Run migrations */
for ( ; v < ARRAY_SIZE(config_migrate_table); v++) {
@ -775,8 +1103,48 @@ config_migrate ( void )
}
/* Update */
update:
htsmsg_set_u32(config, "version", v);
htsmsg_set_str(config, "fullversion", tvheadend_version);
config_save();
return 1;
}
/*
*
*/
static void
config_check_one ( const char *dir )
{
htsmsg_t *c, *e;
htsmsg_field_t *f;
if (!(c = hts_settings_load(dir)))
return;
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (strlen(f->hmf_name) != UUID_HEX_SIZE - 1) {
tvherror("START", "filename %s/%s/%s is invalid", hts_settings_get_root(), dir, f->hmf_name);
exit(1);
}
}
htsmsg_destroy(c);
}
/*
* Perform a simple check for UUID files
*/
static void
config_check ( void )
{
config_check_one("accesscontrol");
config_check_one("channel/config");
config_check_one("channel/tag");
config_check_one("dvr/config");
config_check_one("dvr/log");
config_check_one("dvr/autorec");
config_check_one("esfilter");
}
/* **************************************************************************
@ -784,7 +1152,7 @@ config_migrate ( void )
* *************************************************************************/
void
config_init ( const char *path )
config_init ( const char *path, int backup )
{
struct stat st;
char buf[1024];
@ -829,11 +1197,13 @@ config_init ( const char *path )
/* Store version number */
if (new) {
htsmsg_set_u32(config, "version", ARRAY_SIZE(config_migrate_table));
htsmsg_set_str(config, "fullversion", tvheadend_version);
config_save();
/* Perform migrations */
} else {
config_migrate();
if (config_migrate(backup))
config_check();
}
}

View file

@ -23,7 +23,7 @@
#include "htsmsg.h"
void config_init ( const char *path );
void config_init ( const char *path, int backup );
void config_done ( void );
void config_save ( void );

View file

@ -73,7 +73,7 @@ cron_parse_field
if ((sn - off) >= bits || (en - off) >= bits || mn > bits)
return 1;
if (en < 0) en = sn;
if (mn < 0) mn = 1;
if (mn <= 0) mn = 1;
while (sn <= en) {
if ( (sn % mn) == 0 )
val |= (0x1ULL << (sn - off));
@ -176,7 +176,7 @@ cron_multi_set ( const char *str )
if (line[0] != '#')
if (!cron_set(&cron, line)) {
count++;
cm2 = realloc(cm, sizeof(cm) + sizeof(cron) * count);
cm2 = realloc(cm, sizeof(*cm) + sizeof(cron) * count);
if (cm2 == NULL) {
free(cm);
return NULL;

View file

@ -27,18 +27,39 @@
#include "lang_str.h"
typedef struct dvr_config {
idnode_t dvr_id;
LIST_ENTRY(dvr_config) config_link;
int dvr_enabled;
int dvr_valid;
char *dvr_config_name;
char *dvr_storage;
uint32_t dvr_retention_days;
int dvr_flags;
char *dvr_charset;
char *dvr_charset_id;
char *dvr_postproc;
int dvr_extra_time_pre;
int dvr_extra_time_post;
uint32_t dvr_extra_time_pre;
uint32_t dvr_extra_time_post;
muxer_container_type_t dvr_mc;
muxer_config_t dvr_muxcnf;
int dvr_mc;
muxer_config_t dvr_muxcnf;
int dvr_dir_per_day;
int dvr_channel_dir;
int dvr_channel_in_title;
int dvr_omit_title;
int dvr_date_in_title;
int dvr_time_in_title;
int dvr_whitespace_in_title;
int dvr_title_dir;
int dvr_episode_in_title;
int dvr_clean_title;
int dvr_tag_files;
int dvr_skip_commercials;
int dvr_subtitle_in_title;
int dvr_episode_before_date;
int dvr_episode_duplicate;
/* Series link support */
int dvr_sl_brand_lock;
@ -51,28 +72,16 @@ typedef struct dvr_config {
/* Duplicate detect */
int dvr_dup_detect_episode;
LIST_ENTRY(dvr_config) config_link;
struct dvr_entry_list dvr_entries;
struct access_entry_list dvr_accesses;
} dvr_config_t;
extern struct dvr_config_list dvrconfigs;
extern struct dvr_entry_list dvrentries;
#define DVR_DIR_PER_DAY 0x1
#define DVR_DIR_PER_CHANNEL 0x2
#define DVR_CHANNEL_IN_TITLE 0x4
#define DVR_DATE_IN_TITLE 0x8
#define DVR_TIME_IN_TITLE 0x10
#define DVR_WHITESPACE_IN_TITLE 0x20
#define DVR_DIR_PER_TITLE 0x40
#define DVR_EPISODE_IN_TITLE 0x80
#define DVR_CLEAN_TITLE 0x100
#define DVR_TAG_FILES 0x200
#define DVR_SKIP_COMMERCIALS 0x400
#define DVR_SUBTITLE_IN_TITLE 0x800
#define DVR_EPISODE_BEFORE_DATE 0x1000
#define DVR_EPISODE_DUPLICATE_DETECTION 0x2000
typedef enum {
DVR_PRIO_IMPORTANT,
DVR_PRIO_HIGH,
@ -106,6 +115,8 @@ typedef enum {
typedef struct dvr_entry {
idnode_t de_id;
int de_refcnt; /* Modification is protected under global_lock */
@ -115,7 +126,6 @@ typedef struct dvr_entry {
*/
LIST_ENTRY(dvr_entry) de_global_link;
int de_id;
channel_t *de_channel;
LIST_ENTRY(dvr_entry) de_channel_link;
@ -128,7 +138,8 @@ typedef struct dvr_entry {
* These meta fields will stay valid as long as reference count > 0
*/
char *de_config_name;
dvr_config_t *de_config;
LIST_ENTRY(dvr_entry) de_config_link;
time_t de_start;
time_t de_stop;
@ -141,15 +152,13 @@ typedef struct dvr_entry {
generated yet */
lang_str_t *de_title; /* Title in UTF-8 (from EPG) */
lang_str_t *de_desc; /* Description in UTF-8 (from EPG) */
epg_genre_t de_content_type; /* Content type (from EPG) */
uint32_t de_content_type; /* Content type (from EPG) (only code) */
uint16_t de_dvb_eid;
dvr_prio_t de_pri;
uint32_t de_dont_reschedule;
muxer_container_type_t de_mc;
int de_pri;
int de_dont_reschedule;
int de_mc;
/**
* EPG information / links
@ -214,9 +223,11 @@ typedef struct dvr_entry {
* Autorec entry
*/
typedef struct dvr_autorec_entry {
TAILQ_ENTRY(dvr_autorec_entry) dae_link;
char *dae_id;
idnode_t dae_id;
TAILQ_ENTRY(dvr_autorec_entry) dae_link;
char *dae_name;
char *dae_config_name;
int dae_enabled;
@ -226,11 +237,11 @@ typedef struct dvr_autorec_entry {
char *dae_title;
regex_t dae_title_preg;
epg_genre_t dae_content_type;
uint32_t dae_content_type;
int dae_approx_time; /* Minutes from midnight */
int dae_start; /* Minutes from midnight */
int dae_weekdays;
uint32_t dae_weekdays;
channel_t *dae_channel;
LIST_ENTRY(dvr_autorec_entry) dae_channel_link;
@ -251,6 +262,17 @@ typedef struct dvr_autorec_entry {
int dae_maxduration;
} dvr_autorec_entry_t;
TAILQ_HEAD(dvr_autorec_entry_queue, dvr_autorec_entry);
extern struct dvr_autorec_entry_queue autorec_entries;
/**
*
*/
extern const idclass_t dvr_config_class;
extern const idclass_t dvr_entry_class;
extern const idclass_t dvr_autorec_entry_class;
/**
* Prototypes
@ -258,15 +280,32 @@ typedef struct dvr_autorec_entry {
void dvr_make_title(char *output, size_t outlen, dvr_entry_t *de);
static inline int dvr_config_is_valid(dvr_config_t *cfg)
{ return cfg->dvr_valid; }
static inline int dvr_config_is_default(dvr_config_t *cfg)
{ return cfg->dvr_config_name == NULL || cfg->dvr_config_name[0] == '\0'; }
dvr_config_t *dvr_config_find_by_name(const char *name);
dvr_config_t *dvr_config_find_by_name_default(const char *name);
dvr_config_t *dvr_config_create(const char *name);
dvr_config_t *dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf);
static inline dvr_config_t *dvr_config_find_by_uuid(const char *uuid)
{ return (dvr_config_t*)idnode_find(uuid, &dvr_config_class); }
void dvr_config_delete(const char *name);
void dvr_entry_notify(dvr_entry_t *de);
void dvr_config_save(dvr_config_t *cfg);
static inline int dvr_entry_is_editable(dvr_entry_t *de)
{ return de->de_sched_state == DVR_SCHEDULED; }
static inline int dvr_entry_is_valid(dvr_entry_t *de)
{ return de->de_refcnt > 0; }
int dvr_entry_get_mc(dvr_entry_t *de);
void dvr_entry_save(dvr_entry_t *de);
@ -276,30 +315,37 @@ const char *dvr_entry_schedstatus(dvr_entry_t *de);
void dvr_entry_create_by_autorec(epg_broadcast_t *e, dvr_autorec_entry_t *dae);
dvr_entry_t *dvr_entry_create_by_event
(const char *dvr_config_name,
epg_broadcast_t *e,
time_t start_extra, time_t stop_extra,
const char *creator,
dvr_autorec_entry_t *dae,
dvr_prio_t pri);
void dvr_entry_created(dvr_entry_t *de);
dvr_entry_t *dvr_entry_create
(const char *dvr_config_name,
channel_t *ch, time_t start, time_t stop,
time_t start_extra, time_t stop_extra,
const char *title, const char *description, const char *lang,
epg_genre_t *content_type,
const char *creator, dvr_autorec_entry_t *dae,
dvr_prio_t pri);
dvr_entry_t *
dvr_entry_create ( const char *uuid, htsmsg_t *conf );
dvr_entry_t *dvr_entry_update
(dvr_entry_t *de,
const char* de_title, const char *de_desc, const char *lang,
time_t de_start, time_t de_stop,
time_t de_start_extra, time_t de_stop_extra );
dvr_entry_t *
dvr_entry_create_by_event( const char *dvr_config_uuid,
epg_broadcast_t *e,
time_t start_extra, time_t stop_extra,
const char *creator,
dvr_autorec_entry_t *dae,
dvr_prio_t pri );
dvr_entry_t *
dvr_entry_create_htsp( const char *dvr_config_uuid,
channel_t *ch, time_t start, time_t stop,
time_t start_extra, time_t stop_extra,
const char *title, const char *description,
const char *lang, epg_genre_t *content_type,
const char *creator, dvr_autorec_entry_t *dae,
dvr_prio_t pri );
dvr_entry_t *
dvr_entry_update( dvr_entry_t *de,
const char* de_title, const char *de_desc, const char *lang,
time_t de_start, time_t de_stop,
time_t de_start_extra, time_t de_stop_extra );
void dvr_init(void);
void dvr_config_init(void);
void dvr_done(void);
@ -321,6 +367,9 @@ void dvr_event_updated(epg_broadcast_t *e);
dvr_entry_t *dvr_entry_find_by_id(int id);
static inline dvr_entry_t *dvr_entry_find_by_uuid(const char *uuid)
{ return (dvr_entry_t*)idnode_find(uuid, &dvr_entry_class); }
dvr_entry_t *dvr_entry_find_by_event(epg_broadcast_t *e);
dvr_entry_t *dvr_entry_find_by_event_fuzzy(epg_broadcast_t *e);
@ -333,34 +382,14 @@ dvr_entry_t *dvr_entry_cancel(dvr_entry_t *de);
void dvr_entry_dec_ref(dvr_entry_t *de);
void dvr_storage_set(dvr_config_t *cfg, const char *storage);
void dvr_charset_set(dvr_config_t *cfg, const char *charset);
void dvr_container_set(dvr_config_t *cfg, const char *container);
void dvr_file_permissions_set(dvr_config_t *cfg, int permissions);
void dvr_directory_permissions_set(dvr_config_t *cfg, int permissions);
void dvr_mux_cache_set(dvr_config_t *cfg, int mcache);
void dvr_postproc_set(dvr_config_t *cfg, const char *postproc);
void dvr_retention_set(dvr_config_t *cfg, int days);
void dvr_flags_set(dvr_config_t *cfg, int flags);
void dvr_mux_flags_set(dvr_config_t *cfg, int flags);
void dvr_extra_time_pre_set(dvr_config_t *cfg, int d);
void dvr_extra_time_post_set(dvr_config_t *cfg, int d);
void dvr_entry_delete(dvr_entry_t *de);
void dvr_entry_cancel_delete(dvr_entry_t *de);
htsmsg_t *dvr_entry_class_pri_list(void *o);
htsmsg_t *dvr_entry_class_config_name_list(void *o);
htsmsg_t *dvr_entry_class_duration_list(void *o, const char *not_set, int max);
/**
* Query interface
*/
@ -386,15 +415,23 @@ int dvr_sort_start_ascending(const void *A, const void *B);
/**
*
*/
void dvr_autorec_add(const char *dvr_config_name,
const char *title, const char *channel,
const char *tag, epg_genre_t *content_type,
const int min_duration, const int max_duration,
const char *creator, const char *comment);
void dvr_autorec_add_series_link(const char *dvr_config_name,
epg_broadcast_t *event,
const char *creator, const char *comment);
dvr_autorec_entry_t *
dvr_autorec_create(const char *uuid, htsmsg_t *conf);
dvr_autorec_entry_t *
dvr_autorec_add_series_link(const char *dvr_config_name,
epg_broadcast_t *event,
const char *creator, const char *comment);
void dvr_autorec_save(dvr_autorec_entry_t *dae);
void dvr_autorec_changed(dvr_autorec_entry_t *dae, int purge);
static inline dvr_autorec_entry_t *
dvr_autorec_find_by_uuid(const char *uuid)
{ return (dvr_autorec_entry_t*)idnode_find(uuid, &dvr_autorec_entry_class); }
void dvr_autorec_check_event(epg_broadcast_t *e);
void dvr_autorec_check_brand(epg_brand_t *b);
@ -404,7 +441,7 @@ void dvr_autorec_check_serieslink(epg_serieslink_t *s);
void autorec_destroy_by_channel(channel_t *ch, int delconf);
dvr_autorec_entry_t *autorec_entry_find(const char *id, int create);
void autorec_destroy_by_channel_tag(channel_tag_t *ct, int delconf);
/**
*

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -115,17 +115,16 @@ void dvr_inotify_add ( dvr_entry_t *de )
SKEL_USED(dvr_inotify_entry_skel);
e->path = strdup(e->path);
e->fd = inotify_add_watch(_inot_fd, e->path, EVENT_MASK);
if (e->fd == -1) {
tvhlog(LOG_ERR, "dvr", "failed to add inotify watch to %s (err=%s)",
e->path, strerror(errno));
free(path);
dvr_inotify_del(de);
return;
}
}
LIST_INSERT_HEAD(&e->entries, de, de_inotify_link);
if (e->fd < 0) {
tvhlog(LOG_ERR, "dvr", "failed to add inotify watch to %s (err=%s)",
e->path, strerror(errno));
dvr_inotify_del(de);
}
free(path);
}
@ -146,7 +145,8 @@ void dvr_inotify_del ( dvr_entry_t *de )
LIST_REMOVE(det, de_inotify_link);
if (LIST_FIRST(&e->entries) == NULL) {
RB_REMOVE(&_inot_tree, e, link);
inotify_rm_watch(_inot_fd, e->fd);
if (e->fd >= 0)
inotify_rm_watch(_inot_fd, e->fd);
free(e->path);
free(e);
}
@ -180,7 +180,7 @@ _dvr_inotify_find2
snprintf(path, sizeof(path), "%s/%s", die->path, name);
LIST_FOREACH(de, &die->entries, de_inotify_link)
if (!strcmp(path, de->de_filename))
if (de->de_filename && !strcmp(path, de->de_filename))
break;
return de;
@ -211,7 +211,7 @@ _dvr_inotify_moved
dvr_inotify_del(de);
htsp_dvr_entry_update(de);
dvr_entry_notify(de);
idnode_notify_simple(&de->de_id);
}
/*
@ -239,7 +239,7 @@ _dvr_inotify_moved_all
while ((de = LIST_FIRST(&die->entries))) {
htsp_dvr_entry_update(de);
dvr_entry_notify(de);
idnode_notify_simple(&de->de_id);
dvr_inotify_del(de);
}
}

View file

@ -74,7 +74,7 @@ dvr_rec_subscribe(dvr_entry_t *de)
snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL));
if(de->de_mc == MC_PASS) {
if(dvr_entry_get_mc(de) == MC_PASS) {
streaming_queue_init(&de->de_sq, SMT_PACKET);
de->de_gh = NULL;
de->de_tsfix = NULL;
@ -126,7 +126,7 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
static char *
cleanup_filename(char *s, dvr_config_t *cfg)
{
int i, len = strlen(s), dvr_flags = cfg->dvr_flags;
int i, len = strlen(s);
char *s1;
s1 = intlconv_utf8safestr(cfg->dvr_charset_id, s, len * 2);
@ -148,11 +148,11 @@ cleanup_filename(char *s, dvr_config_t *cfg)
if(s[i] == '/')
s[i] = '-';
else if((dvr_flags & DVR_WHITESPACE_IN_TITLE) &&
else if(cfg->dvr_whitespace_in_title &&
(s[i] == ' ' || s[i] == '\t'))
s[i] = '-';
else if((dvr_flags & DVR_CLEAN_TITLE) &&
else if(cfg->dvr_clean_title &&
((s[i] < 32) || (s[i] > 122) ||
(strchr("/:\\<>|*?'\"", s[i]) != NULL)))
s[i] = '_';
@ -177,7 +177,10 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
struct stat st;
char *filename, *s;
struct tm tm;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
dvr_config_t *cfg = de->de_config;
if (de == NULL)
return -1;
strncpy(path, cfg->dvr_storage, sizeof(path));
path[sizeof(path)-1] = '\0';
@ -187,7 +190,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
path[strlen(path)-1] = '\0';
/* Append per-day directory */
if (cfg->dvr_flags & DVR_DIR_PER_DAY) {
if (cfg->dvr_dir_per_day) {
localtime_r(&de->de_start, &tm);
strftime(fullname, sizeof(fullname), "%F", &tm);
s = cleanup_filename(fullname, cfg);
@ -198,7 +201,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
}
/* Append per-channel directory */
if (cfg->dvr_flags & DVR_DIR_PER_CHANNEL) {
if (cfg->dvr_channel_dir) {
char *chname = strdup(DVR_CH_NAME(de));
s = cleanup_filename(chname, cfg);
free(chname);
@ -211,7 +214,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
// TODO: per-brand, per-season
/* Append per-title directory */
if (cfg->dvr_flags & DVR_DIR_PER_TITLE) {
if (cfg->dvr_title_dir) {
char *title = strdup(lang_str_get(de->de_title, NULL));
s = cleanup_filename(title, cfg);
free(title);
@ -292,7 +295,7 @@ dvr_rec_set_state(dvr_entry_t *de, dvr_rs_state_t newstate, int error)
de->de_errors++;
}
if (notify)
dvr_entry_notify(de);
idnode_notify_simple(&de->de_id);
}
/**
@ -304,10 +307,15 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
const source_info_t *si = &ss->ss_si;
const streaming_start_component_t *ssc;
int i;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
dvr_config_t *cfg = de->de_config;
muxer_container_type_t mc;
mc = de->de_mc;
if (!cfg) {
dvr_rec_fatal_error(de, "Unable to determine config profile");
return -1;
}
mc = dvr_entry_get_mc(de);
de->de_mux = muxer_create(mc, &cfg->dvr_muxcnf);
if(!de->de_mux) {
@ -330,7 +338,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
return -1;
}
if(cfg->dvr_flags & DVR_TAG_FILES && de->de_bcast) {
if(cfg->dvr_tag_files && de->de_bcast) {
if(muxer_write_meta(de->de_mux, de->de_bcast)) {
dvr_rec_fatal_error(de, "Unable to write meta data");
return -1;
@ -427,13 +435,13 @@ static void *
dvr_thread(void *aux)
{
dvr_entry_t *de = aux;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
dvr_config_t *cfg = de->de_config;
streaming_queue_t *sq = &de->de_sq;
streaming_message_t *sm;
th_pkt_t *pkt;
int run = 1;
int started = 0;
int comm_skip = (cfg->dvr_flags & DVR_SKIP_COMMERCIALS);
int comm_skip = cfg->dvr_skip_commercials;
int commercial = COMMERCIAL_UNKNOWN;
pthread_mutex_lock(&sq->sq_mutex);
@ -508,9 +516,8 @@ dvr_thread(void *aux)
dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0);
if(dvr_rec_start(de, sm->sm_data) == 0) {
started = 1;
dvr_entry_notify(de);
idnode_changed(&de->de_id);
htsp_dvr_entry_update(de);
dvr_entry_save(de);
}
pthread_mutex_unlock(&global_lock);
}
@ -666,7 +673,7 @@ dvr_thread_epilog(dvr_entry_t *de)
muxer_destroy(de->de_mux);
de->de_mux = NULL;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
if(cfg->dvr_postproc && de->de_filename)
dvr_config_t *cfg = de->de_config;
if(cfg && cfg->dvr_postproc && de->de_filename)
dvr_spawn_postproc(de,cfg->dvr_postproc);
}

View file

@ -2190,8 +2190,8 @@ htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix )
for (j = 0; j < (major_only ? 1 : 16); j++) {
if (_epg_genre_names[i][j]) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "code", i << 4 | j);
htsmsg_add_str(e, "name", _epg_genre_names[i][j]);
htsmsg_add_u32(e, "key", major_only ? i : (i << 4 | j));
htsmsg_add_str(e, "val", _epg_genre_names[i][j]);
// TODO: use major_prefix
htsmsg_add_msg(m, NULL, e);
}

View file

@ -417,4 +417,5 @@ void epggrab_done ( void )
epggrab_cron = NULL;
free(epggrab_cron_multi);
epggrab_cron_multi = NULL;
epggrab_channel_done();
}

View file

@ -27,6 +27,8 @@
#include <assert.h>
#include <string.h>
SKEL_DECLARE(epggrab_channel_skel, epggrab_channel_t);
/* **************************************************************************
* EPG Grab Channel functions
* *************************************************************************/
@ -189,13 +191,12 @@ epggrab_channel_t *epggrab_channel_find
{
char *s;
epggrab_channel_t *ec;
static epggrab_channel_t *skel = NULL;
if (!skel) skel = calloc(1, sizeof(epggrab_channel_t));
skel->id = tvh_strdupa(id);
SKEL_ALLOC(epggrab_channel_skel);
s = epggrab_channel_skel->id = tvh_strdupa(id);
/* Replace / with # */
// Note: this is a bit of a nasty fix for #1774, but will do for now
s = skel->id;
while (*s) {
if (*s == '/') *s = '#';
s++;
@ -203,18 +204,19 @@ epggrab_channel_t *epggrab_channel_find
/* Find */
if (!create) {
ec = RB_FIND(tree, skel, link, _ch_id_cmp);
ec = RB_FIND(tree, epggrab_channel_skel, link, _ch_id_cmp);
/* Find/Create */
} else {
ec = RB_INSERT_SORTED(tree, skel, link, _ch_id_cmp);
ec = RB_INSERT_SORTED(tree, epggrab_channel_skel, link, _ch_id_cmp);
if (!ec) {
assert(owner);
ec = skel;
ec->id = strdup(skel->id);
ec = epggrab_channel_skel;
SKEL_USED(epggrab_channel_skel);
ec->id = strdup(ec->id);
ec->mod = owner;
skel = NULL;
*save = 1;
return ec;
}
}
return ec;
@ -299,3 +301,9 @@ epggrab_channel_is_ota ( epggrab_channel_t *ec )
{
return ec->mod->type == EPGGRAB_OTA;
}
void
epggrab_channel_done( void )
{
SKEL_FREE(epggrab_channel_skel);
}

View file

@ -713,7 +713,7 @@ static int _eit_tune
// consider changeing it?
for (osl = RB_FIRST(&map->om_svcs); osl != NULL; osl = nxt) {
nxt = RB_NEXT(osl, link);
/* rule: if 5 mux scans fails for this service, remove it */
/* rule: if 5 mux scans fail for this service, remove it */
if (osl->last_tune_count + 5 <= map->om_tune_count ||
!(s = mpegts_service_find_by_uuid(osl->uuid))) {
epggrab_ota_service_del(map, om, osl, 1);

View file

@ -416,13 +416,13 @@ epggrab_ota_kick_cb ( void *p )
[MM_EPG_DISABLE] = NULL,
[MM_EPG_ENABLE] = NULL,
[MM_EPG_FORCE] = NULL,
[MM_EPG_FORCE_EIT] = "eit",
[MM_EPG_FORCE_UK_FREESAT] = "uk_freesat",
[MM_EPG_FORCE_UK_FREEVIEW] = "uk_freeview",
[MM_EPG_FORCE_VIASAT_BALTIC] = "viasat_baltic",
[MM_EPG_FORCE_OPENTV_SKY_UK] = "opentv-skyuk",
[MM_EPG_FORCE_OPENTV_SKY_ITALIA] = "opentv-skyit",
[MM_EPG_FORCE_OPENTV_SKY_AUSAT] = "opentv-ausat",
[MM_EPG_ONLY_EIT] = "eit",
[MM_EPG_ONLY_UK_FREESAT] = "uk_freesat",
[MM_EPG_ONLY_UK_FREEVIEW] = "uk_freeview",
[MM_EPG_ONLY_VIASAT_BALTIC] = "viasat_baltic",
[MM_EPG_ONLY_OPENTV_SKY_UK] = "opentv-skyuk",
[MM_EPG_ONLY_OPENTV_SKY_ITALIA] = "opentv-skyit",
[MM_EPG_ONLY_OPENTV_SKY_AUSAT] = "opentv-ausat",
};
lock_assert(&global_lock);
@ -476,26 +476,25 @@ next_one:
goto done;
}
if (epg_flag != MM_EPG_FORCE) {
/* Check we have modules attached and enabled */
LIST_FOREACH(map, &om->om_modules, om_link) {
if (map->om_module->tune(map, om, mm))
break;
}
if (!map) {
char name[256];
mpegts_mux_nice_name(mm, name, sizeof(name));
tvhdebug("epggrab", "no OTA modules active for %s, check again next time", name);
goto done;
/* Check we have modules attached and enabled */
i = r = 0;
LIST_FOREACH(map, &om->om_modules, om_link) {
if (map->om_module->tune(map, om, mm)) {
i++;
if (modname && !strcmp(modname, map->om_module->id))
r = 1;
}
}
if ((i == 0 || (r == 0 && modname)) && epg_flag != MM_EPG_FORCE) {
char name[256];
mpegts_mux_nice_name(mm, name, sizeof(name));
tvhdebug("epggrab", "no OTA modules active for %s, check again next time", name);
goto done;
}
/* Some init stuff */
free(om->om_force_modname);
if (modname)
om->om_force_modname = strdup(modname);
else
om->om_force_modname = NULL;
om->om_force_modname = modname ? strdup(modname) : NULL;
/* Subscribe to the mux */
if ((r = mpegts_mux_subscribe(mm, "epggrab", SUBSCRIPTION_PRIO_EPG))) {
@ -555,6 +554,8 @@ epggrab_ota_start_cb ( void *p )
pthread_mutex_lock(&epggrab_ota_mutex);
if (!cron_multi_next(epggrab_ota_cron_multi, dispatch_clock, &next))
epggrab_ota_next_arm(next);
else
tvhwarn("epggrab", "ota cron config invalid or unset");
pthread_mutex_unlock(&epggrab_ota_mutex);
}
@ -570,6 +571,8 @@ epggrab_ota_arm ( time_t last )
if (last != (time_t)-1 && last + 1800 > next)
next = last + 1800;
epggrab_ota_next_arm(next);
} else {
tvhwarn("epggrab", "ota cron config invalid or unset");
}
pthread_mutex_unlock(&epggrab_ota_mutex);
@ -615,6 +618,7 @@ epggrab_ota_service_add ( epggrab_ota_map_t *map, epggrab_ota_mux_t *ota,
ota->om_save = 1;
epggrab_ota_service_trace(ota, svcl, "add new");
}
svcl->last_tune_count = map->om_tune_count;
}
void
@ -724,8 +728,8 @@ epggrab_ota_init ( void )
epggrab_ota_initial = 1;
epggrab_ota_timeout = 600;
epggrab_ota_cron = strdup("# Default config (02:04 and 14:04 everyday)\n4 2 * * *\n4 14 * * *");;
epggrab_ota_cron_multi = NULL;
epggrab_ota_cron = strdup("# Default config (02:04 and 14:04 everyday)\n4 2 * * *\n4 14 * * *");
epggrab_ota_cron_multi = cron_multi_set(epggrab_ota_cron);
epggrab_ota_pending_flag = 0;
RB_INIT(&epggrab_ota_all);

View file

@ -54,6 +54,8 @@ epggrab_channel_t *epggrab_channel_find
( epggrab_channel_tree_t *chs, const char *id, int create, int *save,
epggrab_module_t *owner );
void epggrab_channel_done(void);
/* **************************************************************************
* Internal module routines
* *************************************************************************/

View file

@ -587,6 +587,7 @@ esfilter_class_action_enum(void *o)
const idclass_t esfilter_class = {
.ic_class = "esfilter",
.ic_caption = "Elementary Stream Filter",
.ic_event = "esfilter",
.ic_save = esfilter_class_save,
.ic_get_title = esfilter_class_get_title,
.ic_delete = esfilter_class_delete,
@ -1034,7 +1035,7 @@ esfilter_init(void)
for (i = 0; i <= ESF_CLASS_LAST; i++)
TAILQ_INIT(&esfilters[i]);
if (!(c = hts_settings_load_r(1, "esfilter")))
if (!(c = hts_settings_load("esfilter")))
return;
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_field_get_map(f)))

View file

@ -261,6 +261,25 @@ htsmsg_add_str(htsmsg_t *msg, const char *name, const char *str)
f->hmf_str = strdup(str);
}
/*
*
*/
int
htsmsg_set_str(htsmsg_t *msg, const char *name, const char *str)
{
htsmsg_field_t *f = htsmsg_field_find(msg, name);
if (!f)
f = htsmsg_field_add(msg, name, HMF_STR, HMF_ALLOCED | HMF_NAME_ALLOCED);
else {
if (f->hmf_type != HMF_STR)
return 1;
if(f->hmf_flags & HMF_ALLOCED)
free((void *)f->hmf_str);
}
f->hmf_str = strdup(str);
return 0;
}
/*
*
*/

View file

@ -135,6 +135,11 @@ void htsmsg_add_s64(htsmsg_t *msg, const char *name, int64_t s64);
*/
void htsmsg_add_str(htsmsg_t *msg, const char *name, const char *str);
/**
* Add/update a string field
*/
int htsmsg_set_str(htsmsg_t *msg, const char *name, const char *str);
/**
* Add an field where source is a list or map message.
*/

View file

@ -550,13 +550,16 @@ htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp)
channel_tag_t *ct;
service_t *t;
epg_broadcast_t *now, *next = NULL;
int64_t chnum = channel_get_number(ch);
htsmsg_t *out = htsmsg_create_map();
htsmsg_t *tags = htsmsg_create_list();
htsmsg_t *services = htsmsg_create_list();
htsmsg_add_u32(out, "channelId", channel_get_id(ch));
htsmsg_add_u32(out, "channelNumber", channel_get_number(ch));
htsmsg_add_u32(out, "channelNumber", channel_get_major(chnum));
if (channel_get_minor(chnum))
htsmsg_add_u32(out, "channelNumberMinor", channel_get_minor(chnum));
htsmsg_add_str(out, "channelName", channel_get_name(ch));
if(ch->ch_icon != NULL) {
@ -652,9 +655,8 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method)
htsmsg_t *out = htsmsg_create_map();
const char *s = NULL, *error = NULL;
const char *p;
dvr_config_t *cfg;
htsmsg_add_u32(out, "id", de->de_id);
htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
if (de->de_channel)
htsmsg_add_u32(out, "channel", channel_get_id(de->de_channel));
@ -666,11 +668,9 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method)
if( de->de_desc && (s = lang_str_get(de->de_desc, NULL)))
htsmsg_add_str(out, "description", s);
if( de->de_filename && de->de_config_name ) {
if ((cfg = dvr_config_find_by_name_default(de->de_config_name))) {
if ((p = tvh_strbegins(de->de_filename, cfg->dvr_storage)))
htsmsg_add_str(out, "path", p);
}
if( de->de_filename && de->de_config ) {
if ((p = tvh_strbegins(de->de_filename, de->de_config->dvr_storage)))
htsmsg_add_str(out, "path", p);
}
switch(de->de_sched_state) {
@ -796,7 +796,7 @@ htsp_build_event
}
if((de = dvr_entry_find_by_event(e)) != NULL) {
htsmsg_add_u32(out, "dvrId", de->de_id);
htsmsg_add_u32(out, "dvrId", idnode_get_short_uuid(&de->de_id));
}
if ((n = epg_broadcast_get_next(e)))
@ -1217,9 +1217,9 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
desc = "";
// create the dvr entry
de = dvr_entry_create(dvr_config_name, ch, start, stop,
start_extra, stop_extra,
title, desc, lang, 0, creator, NULL, priority);
de = dvr_entry_create_htsp(dvr_config_name, ch, start, stop,
start_extra, stop_extra,
title, desc, lang, 0, creator, NULL, priority);
/* Event timer */
} else {
@ -1238,7 +1238,7 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
case DVR_RECORDING:
case DVR_MISSED_TIME:
case DVR_COMPLETED:
htsmsg_add_u32(out, "id", de->de_id);
htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
htsmsg_add_u32(out, "success", 1);
break;
case DVR_NOSTATE:
@ -2531,7 +2531,7 @@ void
htsp_dvr_entry_delete(dvr_entry_t *de)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "id", de->de_id);
htsmsg_add_u32(m, "id", idnode_get_short_uuid(&de->de_id));
htsmsg_add_str(m, "method", "dvrEntryDelete");
htsp_async_send(m, HTSP_ASYNC_ON);
}

View file

@ -324,9 +324,13 @@ http_error(http_connection_t *hc, int error)
"<HTML><HEAD>\r\n"
"<TITLE>%d %s</TITLE>\r\n"
"</HEAD><BODY>\r\n"
"<H1>%d %s</H1>\r\n"
"</BODY></HTML>\r\n",
error, errtxt, error, errtxt);
"<H1>%d %s</H1>\r\n",
error, errtxt, error, errtxt);
if (error == HTTP_STATUS_UNAUTHORIZED)
htsbuf_qprintf(&hc->hc_reply, "<P><A HREF=\"/\">Default Login</A></P>");
htsbuf_qprintf(&hc->hc_reply, "</BODY></HTML>\r\n");
http_send_reply(hc, error, "text/html", NULL, NULL, 0);
}
@ -403,11 +407,14 @@ static int http_access_verify_ticket(http_connection_t *hc)
{
const char *ticket_id = http_arg_get(&hc->hc_req_args, "ticket");
if (hc->hc_ticket)
return 0;
if(!access_ticket_verify(ticket_id, hc->hc_url)) {
char addrstr[50];
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50);
tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s",
addrstr, ticket_id, hc->hc_url);
hc->hc_ticket = 1;
return 0;
}
return -1;
@ -422,8 +429,14 @@ http_access_verify(http_connection_t *hc, int mask)
if (!http_access_verify_ticket(hc))
return 0;
return access_verify(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer, mask);
if (hc->hc_access == NULL) {
hc->hc_access = access_get(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer);
if (hc->hc_access == NULL)
return -1;
}
return access_verify2(hc->hc_access, mask);
}
/**
@ -431,21 +444,24 @@ http_access_verify(http_connection_t *hc, int mask)
*/
int
http_access_verify_channel(http_connection_t *hc, int mask,
struct channel *ch)
struct channel *ch, int ticket)
{
access_t *a;
int res = -1;
assert(ch);
if (!http_access_verify_ticket(hc))
if (ticket && !http_access_verify_ticket(hc))
return 0;
a = access_get(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer);
if (channel_access(ch, a, hc->hc_username))
if (hc->hc_access == NULL) {
hc->hc_access = access_get(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer);
if (hc->hc_access == NULL)
return -1;
}
if (channel_access(ch, hc->hc_access, hc->hc_username))
res = 0;
access_destroy(a);
return res;
}
@ -464,6 +480,9 @@ http_exec(http_connection_t *hc, http_path_t *hp, char *remain)
err = HTTP_STATUS_UNAUTHORIZED;
else
err = hp->hp_callback(hc, remain, hp->hp_opaque);
access_destroy(hc->hc_access);
hc->hc_access = NULL;
hc->hc_ticket = 0;
if(err == -1)
return 1;

View file

@ -22,6 +22,7 @@
#include "htsbuf.h"
#include "url.h"
#include "tvhpoll.h"
#include "access.h"
struct channel;
@ -130,6 +131,8 @@ typedef struct http_connection {
char *hc_username;
char *hc_password;
access_t *hc_access;
int hc_ticket;
struct config_head *hc_user_config;
@ -206,7 +209,8 @@ void http_server_register(void);
void http_server_done(void);
int http_access_verify(http_connection_t *hc, int mask);
int http_access_verify_channel(http_connection_t *hc, int mask, struct channel *ch);
int http_access_verify_channel(http_connection_t *hc, int mask,
struct channel *ch, int ticket);
void http_deescape(char *s);

View file

@ -155,7 +155,7 @@ idnode_insert(idnode_t *in, const char *uuid, const idclass_t *class, int flags)
idclass_register(class); // Note: we never actually unregister
/* Fire event */
idnode_notify(in, NULL, 0, 1);
idnode_notify_simple(in);
return 0;
}
@ -169,7 +169,7 @@ idnode_unlink(idnode_t *in)
lock_assert(&global_lock);
RB_REMOVE(&idnodes, in, in_link);
tvhtrace("idnode", "unlink node %s", idnode_uuid_as_str(in));
idnode_notify(in, NULL, 0, 1);
idnode_notify_simple(in);
}
/**
@ -227,7 +227,7 @@ idnode_get_short_uuid (const idnode_t *in)
const char *
idnode_uuid_as_str(const idnode_t *in)
{
static tvh_uuid_t ret[16];
static tvh_uuid_t __thread ret[16];
static uint8_t p = 0;
bin2hex(ret[p].hex, sizeof(ret[p].hex), in->in_uuid, sizeof(in->in_uuid));
const char *s = ret[p].hex;
@ -366,10 +366,94 @@ idnode_get_u32
*u32 = *(int*)ptr;
return 0;
case PT_U16:
*u32 = *(uint32_t*)ptr;
*u32 = *(uint16_t*)ptr;
return 0;
case PT_U32:
*u32 = *(uint16_t*)ptr;
*u32 = *(uint32_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as signed 64-bit int
*/
int
idnode_get_s64
( idnode_t *self, const char *key, int64_t *s64 )
{
const property_t *p = idnode_find_prop(self, key);
if (p->islist) return 1;
if (p) {
const void *ptr;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_INT:
case PT_BOOL:
*s64 = *(int*)ptr;
return 0;
case PT_U16:
*s64 = *(uint16_t*)ptr;
return 0;
case PT_U32:
*s64 = *(uint32_t*)ptr;
return 0;
case PT_S64:
*s64 = *(int64_t*)ptr;
return 0;
case PT_DBL:
*s64 = *(double*)ptr;
return 0;
case PT_TIME:
*s64 = *(time_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as double
*/
int
idnode_get_dbl
( idnode_t *self, const char *key, double *dbl )
{
const property_t *p = idnode_find_prop(self, key);
if (p->islist) return 1;
if (p) {
const void *ptr;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_INT:
case PT_BOOL:
*dbl = *(int*)ptr;
return 0;
case PT_U16:
*dbl = *(uint16_t*)ptr;
return 0;
case PT_U32:
*dbl = *(uint32_t*)ptr;
return 0;
case PT_S64:
*dbl = *(int64_t*)ptr;
return 0;
case PT_DBL:
*dbl = *(double *)ptr;
return 0;
case PT_TIME:
*dbl = *(time_t*)ptr;
return 0;
default:
break;
@ -388,8 +472,11 @@ idnode_get_bool
const property_t *p = idnode_find_prop(self, key);
if (p->islist) return 1;
if (p) {
void *ptr = self;
ptr += p->off;
const void *ptr;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_BOOL:
*b = *(int*)ptr;
@ -401,6 +488,32 @@ idnode_get_bool
return 1;
}
/*
* Get field as time
*/
int
idnode_get_time
( idnode_t *self, const char *key, time_t *tm )
{
const property_t *p = idnode_find_prop(self, key);
if (p->islist) return 1;
if (p) {
const void *ptr;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_TIME:
*tm = *(time_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/* **************************************************************************
* Lookup
* *************************************************************************/
@ -414,6 +527,8 @@ idnode_find(const char *uuid, const idclass_t *idc)
idnode_t skel, *r;
tvhtrace("idnode", "find node %s class %s", uuid, idc ? idc->ic_class : NULL);
if(uuid == NULL || strlen(uuid) != UUID_HEX_SIZE - 1)
return NULL;
if(hex2bin(skel.in_uuid, sizeof(skel.in_uuid), uuid))
return NULL;
r = RB_FIND(&idnodes, &skel, in_link, in_cmp);
@ -464,6 +579,8 @@ idnode_cmp_title
return strcmp(sa ?: "", sb ?: "");
}
#define safecmp(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0))
static int
idnode_cmp_sort
( const void *a, const void *b, void *s )
@ -493,36 +610,105 @@ idnode_cmp_sort
{
int r;
const char *stra = tvh_strdupa(idnode_get_str(ina, sort->key) ?: "");
const char *strb = idnode_get_str(inb, sort->key);
const char *strb = idnode_get_str(inb, sort->key) ?: "";
if (sort->dir == IS_ASC)
r = strcmp(stra ?: "", strb ?: "");
r = strcmp(stra, strb);
else
r = strcmp(strb ?: "", stra ?: "");
r = strcmp(strb, stra);
return r;
}
break;
case PT_INT:
case PT_U16:
case PT_U32:
case PT_BOOL:
case PT_PERM:
{
int32_t i32a = 0, i32b = 0;
idnode_get_u32(ina, sort->key, (uint32_t *)&i32a);
idnode_get_u32(inb, sort->key, (uint32_t *)&i32b);
if (sort->dir == IS_ASC)
return safecmp(i32a, i32b);
else
return safecmp(i32b, i32a);
}
break;
case PT_U32:
{
uint32_t u32a = 0, u32b = 0;
idnode_get_u32(ina, sort->key, &u32a);
idnode_get_u32(inb, sort->key, &u32b);
if (sort->dir == IS_ASC)
return u32a - u32b;
return safecmp(u32a, u32b);
else
return u32b - u32a;
return safecmp(u32b, u32a);
}
break;
case PT_S64:
{
int64_t s64a = 0, s64b = 0;
idnode_get_s64(ina, sort->key, &s64a);
idnode_get_s64(inb, sort->key, &s64b);
if (sort->dir == IS_ASC)
return safecmp(s64a, s64b);
else
return safecmp(s64b, s64a);
}
break;
case PT_DBL:
// TODO
{
double dbla = 0, dblb = 0;
idnode_get_dbl(ina, sort->key, &dbla);
idnode_get_dbl(inb, sort->key, &dblb);
if (sort->dir == IS_ASC)
return safecmp(dbla, dblb);
else
return safecmp(dblb, dbla);
}
break;
case PT_TIME:
{
time_t ta = 0, tb = 0;
idnode_get_time(ina, sort->key, &ta);
idnode_get_time(inb, sort->key, &tb);
if (sort->dir == IS_ASC)
return safecmp(ta, tb);
else
return safecmp(tb, ta);
}
break;
case PT_LANGSTR:
// TODO?
case PT_NONE:
break;
}
return 0;
}
static void
idnode_filter_init
( idnode_t *in, idnode_filter_t *filter )
{
idnode_filter_ele_t *f;
const property_t *p;
LIST_FOREACH(f, filter, link) {
if (f->type == IF_NUM) {
p = idnode_find_prop(in, f->key);
if (p) {
if (p->type == PT_U32 || p->type == PT_S64 ||
p->type == PT_TIME) {
int64_t v = f->u.n.n;
if (p->intsplit != f->u.n.intsplit) {
v = (v / (f->u.n.intsplit <= 0 ? 1 : 0)) * p->intsplit;
f->u.n.n = v;
}
}
}
}
f->checked = 1;
}
}
int
idnode_filter
( idnode_t *in, idnode_filter_t *filter )
@ -530,6 +716,8 @@ idnode_filter
idnode_filter_ele_t *f;
LIST_FOREACH(f, filter, link) {
if (!f->checked)
idnode_filter_init(in, filter);
if (f->type == IF_STR) {
const char *str;
str = idnode_get_display(in, idnode_find_prop(in, f->key));
@ -558,12 +746,32 @@ idnode_filter
break;
}
} else if (f->type == IF_NUM || f->type == IF_BOOL) {
uint32_t u32;
int64_t a, b;
if (idnode_get_u32(in, f->key, &u32))
if (idnode_get_s64(in, f->key, &a))
return 1;
a = u32;
b = (f->type == IF_NUM) ? f->u.n : f->u.b;
b = (f->type == IF_NUM) ? f->u.n.n : f->u.b;
switch (f->comp) {
case IC_IN:
case IC_RE:
break; // Note: invalid
case IC_EQ:
if (a != b)
return 1;
break;
case IC_LT:
if (a > b)
return 1;
break;
case IC_GT:
if (a < b)
return 1;
break;
}
} else if (f->type == IF_DBL) {
double a, b;
if (idnode_get_dbl(in, f->key, &a))
return 1;
b = f->u.dbl;
switch (f->comp) {
case IC_IN:
case IC_RE:
@ -607,13 +815,26 @@ idnode_filter_add_str
void
idnode_filter_add_num
( idnode_filter_t *filt, const char *key, int64_t val, int comp )
( idnode_filter_t *filt, const char *key, int64_t val, int comp, int64_t intsplit )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_NUM;
ele->comp = comp;
ele->u.n = val;
ele->u.n.n = val;
ele->u.n.intsplit = intsplit;
LIST_INSERT_HEAD(filt, ele, link);
}
void
idnode_filter_add_dbl
( idnode_filter_t *filt, const char *key, double dbl, int comp )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_DBL;
ele->comp = comp;
ele->u.dbl = dbl;
LIST_INSERT_HEAD(filt, ele, link);
}
@ -742,7 +963,7 @@ idnode_write0 ( idnode_t *self, htsmsg_t *c, int optmask, int dosave )
if (save && dosave)
idnode_savefn(self);
if (dosave)
idnode_notify(self, NULL, 0, 0);
idnode_notify_simple(self);
// Note: always output event if "dosave", reason is that UI updates on
// these, but there are some subtle cases where it will expect
// an update and not get one. This include fields being set for
@ -751,19 +972,23 @@ idnode_write0 ( idnode_t *self, htsmsg_t *c, int optmask, int dosave )
return save;
}
void
idnode_changed( idnode_t *self )
{
idnode_notify_simple(self);
idnode_savefn(self);
}
/* **************************************************************************
* Read
* *************************************************************************/
/*
* Save
*/
void
idnode_read0 ( idnode_t *self, htsmsg_t *c, int optmask )
idnode_read0 ( idnode_t *self, htsmsg_t *c, htsmsg_t *list, int optmask )
{
const idclass_t *idc = self->in_class;
for (; idc; idc = idc->ic_super)
prop_read_values(self, idc->ic_properties, c, optmask, NULL);
prop_read_values(self, idc->ic_properties, c, list, optmask);
}
/**
@ -771,11 +996,11 @@ idnode_read0 ( idnode_t *self, htsmsg_t *c, int optmask )
*/
static void
add_params
(struct idnode *self, const idclass_t *ic, htsmsg_t *p, int optmask, htsmsg_t *inc)
(struct idnode *self, const idclass_t *ic, htsmsg_t *p, htsmsg_t *list, int optmask)
{
/* Parent first */
if(ic->ic_super != NULL)
add_params(self, ic->ic_super, p, optmask, inc);
add_params(self, ic->ic_super, p, list, optmask);
/* Seperator (if not empty) */
#if 0
@ -788,14 +1013,14 @@ add_params
#endif
/* Properties */
prop_serialize(self, ic->ic_properties, p, optmask, inc);
prop_serialize(self, ic->ic_properties, p, list, optmask);
}
static htsmsg_t *
idnode_params (const idclass_t *idc, idnode_t *self, int optmask)
idnode_params (const idclass_t *idc, idnode_t *self, htsmsg_t *list, int optmask)
{
htsmsg_t *p = htsmsg_create_list();
add_params(self, idc, p, optmask, NULL);
add_params(self, idc, p, list, optmask);
return p;
}
@ -821,17 +1046,59 @@ idclass_get_class (const idclass_t *idc)
return NULL;
}
static const char *
idclass_get_event (const idclass_t *idc)
{
while (idc) {
if (idc->ic_event)
return idc->ic_event;
idc = idc->ic_super;
}
return NULL;
}
static const char *
idclass_get_order (const idclass_t *idc)
{
while (idc) {
if (idc->ic_class)
if (idc->ic_order)
return idc->ic_order;
idc = idc->ic_super;
}
return NULL;
}
static htsmsg_t *
idclass_get_property_groups (const idclass_t *idc)
{
const property_group_t *g;
htsmsg_t *e, *m;
int count;
while (idc) {
if (idc->ic_groups) {
m = htsmsg_create_list();
count = 0;
for (g = idc->ic_groups; g->number && g->name; g++) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "number", g->number);
htsmsg_add_str(e, "name", g->name);
if (g->parent)
htsmsg_add_u32(e, "parent", g->parent);
if (g->column)
htsmsg_add_u32(e, "column", g->column);
htsmsg_add_msg(m, NULL, e);
count++;
}
if (count)
return m;
htsmsg_destroy(m);
break;
}
idc = idc->ic_super;
}
return NULL;
}
static int
ic_cmp ( const idclass_link_t *a, const idclass_link_t *b )
{
@ -870,7 +1137,7 @@ idclass_find ( const char *class )
* Just get the class definition
*/
htsmsg_t *
idclass_serialize0(const idclass_t *idc, int optmask)
idclass_serialize0(const idclass_t *idc, htsmsg_t *list, int optmask)
{
const char *s;
htsmsg_t *p, *m = htsmsg_create_map();
@ -880,11 +1147,15 @@ idclass_serialize0(const idclass_t *idc, int optmask)
htsmsg_add_str(m, "caption", s);
if ((s = idclass_get_class(idc)))
htsmsg_add_str(m, "class", s);
if ((s = idclass_get_event(idc)))
htsmsg_add_str(m, "event", s);
if ((s = idclass_get_order(idc)))
htsmsg_add_str(m, "order", s);
if ((p = idclass_get_property_groups(idc)))
htsmsg_add_msg(m, "groups", p);
/* Props */
if ((p = idnode_params(idc, NULL, optmask)))
if ((p = idnode_params(idc, NULL, list, optmask)))
htsmsg_add_msg(m, "props", p);
return m;
@ -894,7 +1165,7 @@ idclass_serialize0(const idclass_t *idc, int optmask)
*
*/
htsmsg_t *
idnode_serialize0(idnode_t *self, int optmask)
idnode_serialize0(idnode_t *self, htsmsg_t *list, int optmask)
{
const idclass_t *idc = self->in_class;
const char *uuid, *s;
@ -908,16 +1179,32 @@ idnode_serialize0(idnode_t *self, int optmask)
htsmsg_add_str(m, "caption", s);
if ((s = idclass_get_class(idc)))
htsmsg_add_str(m, "class", s);
if ((s = idclass_get_event(idc)))
htsmsg_add_str(m, "event", s);
htsmsg_add_msg(m, "params", idnode_params(idc, self, optmask));
htsmsg_add_msg(m, "params", idnode_params(idc, self, list, optmask));
return m;
}
/* **************************************************************************
* Notifcation
* Notification
* *************************************************************************/
/**
* Delayed notification
*/
static void
idnode_notify_delayed ( idnode_t *in, const char *uuid, const char *event )
{
pthread_mutex_lock(&idnode_mutex);
if (!idnode_queue)
idnode_queue = htsmsg_create_map();
htsmsg_set_str(idnode_queue, uuid, event);
pthread_cond_signal(&idnode_cond);
pthread_mutex_unlock(&idnode_mutex);
}
/**
* Update internal event pipes
*/
@ -927,11 +1214,8 @@ idnode_notify_event ( idnode_t *in )
const idclass_t *ic = in->in_class;
const char *uuid = idnode_uuid_as_str(in);
while (ic) {
if (ic->ic_event) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", uuid);
notify_by_msg(ic->ic_event, m);
}
if (ic->ic_event)
idnode_notify_delayed(in, uuid, ic->ic_event);
ic = ic->ic_super;
}
}
@ -941,38 +1225,37 @@ idnode_notify_event ( idnode_t *in )
*/
void
idnode_notify
(idnode_t *in, const char *chn, int force, int event)
(idnode_t *in, int event)
{
const char *uuid = idnode_uuid_as_str(in);
if (!tvheadend_running)
return;
/* Forced */
if (chn || force) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", uuid);
notify_by_msg(chn ?: "idnodeUpdated", m);
/* Immediate */
if (!event) {
const idclass_t *ic = in->in_class;
while (ic) {
if (ic->ic_event) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", uuid);
notify_by_msg(ic->ic_event, m);
}
ic = ic->ic_super;
}
/* Rate-limited */
} else {
pthread_mutex_lock(&idnode_mutex);
if (!idnode_queue)
idnode_queue = htsmsg_create_map();
htsmsg_set_u32(idnode_queue, uuid, 1);
pthread_cond_signal(&idnode_cond);
pthread_mutex_unlock(&idnode_mutex);
}
/* Send event */
if (event)
idnode_notify_event(in);
}
}
void
idnode_notify_simple (void *in)
{
idnode_notify(in, NULL, 0, 0);
idnode_notify(in, 1);
}
void
@ -981,7 +1264,7 @@ idnode_notify_title_changed (void *in)
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", idnode_uuid_as_str(in));
htsmsg_add_str(m, "text", idnode_get_title(in));
notify_by_msg("idnodeUpdated", m);
notify_by_msg("title", m);
idnode_notify_event(in);
}
@ -994,6 +1277,7 @@ idnode_thread ( void *p )
idnode_t *node;
htsmsg_t *m, *q = NULL;
htsmsg_field_t *f;
const char *event;
pthread_mutex_lock(&idnode_mutex);
@ -1012,13 +1296,13 @@ idnode_thread ( void *p )
pthread_mutex_lock(&global_lock);
HTSMSG_FOREACH(f, q) {
node = idnode_find(f->hmf_name, NULL);
m = htsmsg_create_map();
node = idnode_find(f->hmf_name, NULL);
event = htsmsg_field_get_str(f);
m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", f->hmf_name);
if (node)
notify_by_msg("idnodeUpdated", m);
else
notify_by_msg("idnodeDeleted", m);
if (!node)
htsmsg_add_u32(m, "removed", 1);
notify_by_msg(event, m);
}
/* Finished */

View file

@ -26,7 +26,7 @@
#include <regex.h>
struct htsmsg;
struct access;
typedef struct idnode idnode_t;
/*
@ -39,17 +39,29 @@ typedef struct idnode_set
size_t is_count; ///< Current usage of is_array
} idnode_set_t;
/*
* Property groups
*/
typedef struct property_group
{
const char *name;
uint32_t number;
uint32_t parent;
uint32_t column;
} property_group_t;
/*
* Class definition
*/
typedef struct idclass idclass_t;
struct idclass {
const struct idclass *ic_super; /// Parent class
const char *ic_class; /// Class name
const char *ic_caption; /// Class description
const char *ic_order; /// Property order (comma separated)
const property_t *ic_properties; /// Property list
const char *ic_event; /// Events to fire on add/delete/title
const struct idclass *ic_super; ///< Parent class
const char *ic_class; ///< Class name
const char *ic_caption; ///< Class description
const char *ic_order; ///< Property order (comma separated)
const property_group_t *ic_groups; ///< Groups for visual representation
const property_t *ic_properties; ///< Property list
const char *ic_event; ///< Events to fire on add/delete/title
/* Callbacks */
idnode_set_t *(*ic_get_childs) (idnode_t *self);
@ -58,6 +70,7 @@ struct idclass {
void (*ic_delete) (idnode_t *self);
void (*ic_moveup) (idnode_t *self);
void (*ic_movedown) (idnode_t *self);
int (*ic_perm) (idnode_t *self, struct access *a, htsmsg_t *msg_to_write);
};
/*
@ -87,16 +100,22 @@ typedef struct idnode_filter_ele
{
LIST_ENTRY(idnode_filter_ele) link; ///< List link
int checked;
char *key; ///< Filter key
enum {
IF_STR,
IF_NUM,
IF_DBL,
IF_BOOL
} type; ///< Filter type
union {
int b;
char *s;
int64_t n;
struct {
int64_t n;
int64_t intsplit;
} n;
double dbl;
regex_t re;
} u; ///< Filter data
enum {
@ -128,36 +147,50 @@ void idnode_delete (idnode_t *in);
void idnode_moveup (idnode_t *in);
void idnode_movedown (idnode_t *in);
void idnode_changed (idnode_t *in);
void *idnode_find (const char *uuid, const idclass_t *idc);
idnode_set_t *idnode_find_all(const idclass_t *idc);
#define idnode_updated(in) idnode_notify(in, NULL, 0, 0)
void idnode_notify
(idnode_t *in, const char *chn, int force, int event);
void idnode_notify (idnode_t *in, int event);
void idnode_notify_simple (void *in);
void idnode_notify_title_changed (void *in);
void idclass_register ( const idclass_t *idc );
const idclass_t *idclass_find ( const char *name );
htsmsg_t *idclass_serialize0 (const idclass_t *idc, int optmask);
htsmsg_t *idnode_serialize0 (idnode_t *self, int optmask);
void idnode_read0 (idnode_t *self, htsmsg_t *m, int optmask);
htsmsg_t *idclass_serialize0 (const idclass_t *idc, htsmsg_t *list, int optmask);
htsmsg_t *idnode_serialize0 (idnode_t *self, htsmsg_t *list, int optmask);
void idnode_read0 (idnode_t *self, htsmsg_t *m, htsmsg_t *list, int optmask);
int idnode_write0 (idnode_t *self, htsmsg_t *m, int optmask, int dosave);
#define idclass_serialize(idc) idclass_serialize0(idc, 0)
#define idnode_serialize(in) idnode_serialize0(in, 0)
#define idclass_serialize(idc) idclass_serialize0(idc, NULL, 0)
#define idnode_serialize(in) idnode_serialize0(in, NULL, 0)
#define idnode_load(in, m) idnode_write0(in, m, PO_NOSAVE, 0)
#define idnode_save(in, m) idnode_read0(in, m, PO_NOSAVE | PO_USERAW)
#define idnode_save(in, m) idnode_read0(in, m, NULL, PO_NOSAVE | PO_USERAW)
#define idnode_update(in, m) idnode_write0(in, m, PO_RDONLY | PO_WRONCE, 1)
static inline int
idnode_perm(idnode_t *self, struct access *a, htsmsg_t *msg_to_write)
{
if (self->in_class->ic_perm)
return self->in_class->ic_perm(self, a, msg_to_write);
return 0;
}
const char *idnode_get_str (idnode_t *self, const char *key );
int idnode_get_u32 (idnode_t *self, const char *key, uint32_t *u32);
int idnode_get_s64 (idnode_t *self, const char *key, int64_t *s64);
int idnode_get_dbl (idnode_t *self, const char *key, double *dbl);
int idnode_get_bool(idnode_t *self, const char *key, int *b);
int idnode_get_time(idnode_t *self, const char *key, time_t *tm);
void idnode_filter_add_str
(idnode_filter_t *f, const char *k, const char *v, int t);
void idnode_filter_add_num
(idnode_filter_t *f, const char *k, int64_t s64, int t);
(idnode_filter_t *f, const char *k, int64_t s64, int t, int64_t intsplit);
void idnode_filter_add_dbl
(idnode_filter_t *f, const char *k, double dbl, int t);
void idnode_filter_add_bool
(idnode_filter_t *f, const char *k, int b, int t);
void idnode_filter_clear

View file

@ -388,7 +388,7 @@ htsmsg_t *
imagecache_get_config ( void )
{
htsmsg_t *m = htsmsg_create_map();
prop_read_values(&imagecache_conf, imagecache_props, m, 0, NULL);
prop_read_values(&imagecache_conf, imagecache_props, m, NULL, 0);
return m;
}

View file

@ -22,6 +22,7 @@
#include <pthread.h>
struct imagecache_config {
int __unused__; // to avoid assert in prop.c (first member should be idnode_t)
int enabled;
int ignore_sslcert;
uint32_t ok_period;

View file

@ -43,9 +43,6 @@ tvh_hardware_create0
/* Load config */
if (conf)
idnode_load(&th->th_id, conf);
/* Update */
notify_reload("hardware");
return o;
}
@ -60,7 +57,6 @@ tvh_hardware_delete ( tvh_hardware_t *th )
// TODO
LIST_REMOVE(th, th_link);
idnode_unlink(&th->th_id);
notify_reload("hardware");
}
/*

View file

@ -312,15 +312,15 @@ enum mpegts_mux_epg_flag
MM_EPG_DISABLE,
MM_EPG_ENABLE,
MM_EPG_FORCE,
MM_EPG_FORCE_EIT,
MM_EPG_FORCE_UK_FREESAT,
MM_EPG_FORCE_UK_FREEVIEW,
MM_EPG_FORCE_VIASAT_BALTIC,
MM_EPG_FORCE_OPENTV_SKY_UK,
MM_EPG_FORCE_OPENTV_SKY_ITALIA,
MM_EPG_FORCE_OPENTV_SKY_AUSAT,
MM_EPG_ONLY_EIT,
MM_EPG_ONLY_UK_FREESAT,
MM_EPG_ONLY_UK_FREEVIEW,
MM_EPG_ONLY_VIASAT_BALTIC,
MM_EPG_ONLY_OPENTV_SKY_UK,
MM_EPG_ONLY_OPENTV_SKY_ITALIA,
MM_EPG_ONLY_OPENTV_SKY_AUSAT,
};
#define MM_EPG_LAST MM_EPG_FORCE_OPENTV_SKY_AUSAT
#define MM_EPG_LAST MM_EPG_ONLY_OPENTV_SKY_AUSAT
/* Multiplex */
struct mpegts_mux
@ -411,6 +411,7 @@ struct mpegts_mux
int mm_enabled;
int mm_epg;
char *mm_charset;
int mm_pmt_06_ac3;
};
/* Service */
@ -424,6 +425,7 @@ struct mpegts_service
uint16_t s_dvb_service_id;
uint16_t s_dvb_channel_num;
uint16_t s_dvb_channel_minor;
char *s_dvb_svcname;
char *s_dvb_provider;
char *s_dvb_cridauth;
@ -437,7 +439,7 @@ struct mpegts_service
*/
int s_dvb_eit_enable;
uint16_t s_dvb_opentv_chnum;
uint64_t s_dvb_opentv_chnum;
/*
* Link to carrying multiplex and active adapter
@ -562,6 +564,9 @@ struct mpegts_input
/* Active sources */
LIST_HEAD(,mpegts_mux_instance) mi_mux_active;
LIST_HEAD(,service) mi_transports;
mpegts_mux_t **mi_destroyed_muxes;
int mi_destroyed_muxes_count;
/* Table processing */
pthread_t mi_table_tid;

View file

@ -34,7 +34,7 @@
SKEL_DECLARE(mpegts_table_state_skel, struct mpegts_table_state);
static int
psi_parse_pmt(mpegts_service_t *t, const uint8_t *ptr, int len);
psi_parse_pmt(mpegts_mux_t *mux, mpegts_service_t *t, const uint8_t *ptr, int len);
/* **************************************************************************
* Lookup tables
@ -695,7 +695,7 @@ dvb_pmt_callback
tvhdebug("pmt", "sid %04X (%d)", sid, sid);
pthread_mutex_lock(&s->s_stream_mutex);
had_components = !!TAILQ_FIRST(&s->s_components);
r = psi_parse_pmt(s, ptr, len);
r = psi_parse_pmt(mt->mt_mux, s, ptr, len);
pthread_mutex_unlock(&s->s_stream_mutex);
if (r)
service_restart((service_t*)s, had_components);
@ -989,8 +989,7 @@ dvb_sdt_callback
/* Save details */
if (save) {
idnode_updated(&s->s_id);
s->s_config_save((service_t*)s);
idnode_changed(&s->s_id);
service_refresh_channel((service_t*)s);
}
}
@ -1071,11 +1070,9 @@ atsc_vct_callback
tvh_str_set(&s->s_dvb_svcname, chname);
save = 1;
}
if (s->s_dvb_channel_num != maj) {
// TODO: ATSC channel numbering is plain weird!
// could shift the major (*100 or something) and append
// minor, but that'll probably confuse people, as will this!
if (s->s_dvb_channel_num != maj || s->s_dvb_channel_minor != min) {
s->s_dvb_channel_num = maj;
s->s_dvb_channel_minor = min;
save = 1;
}
@ -1280,7 +1277,7 @@ psi_desc_teletext(mpegts_service_t *t, const uint8_t *ptr, int size,
*/
static int
psi_parse_pmt
(mpegts_service_t *t, const uint8_t *ptr, int len)
(mpegts_mux_t *mux, mpegts_service_t *t, const uint8_t *ptr, int len)
{
int ret = 0;
uint16_t pcr_pid, pid;
@ -1377,7 +1374,7 @@ psi_parse_pmt
case 0x06:
/* 0x06 is Chinese Cable TV AC-3 audio track */
/* but mark it so only when no more descriptors exist */
if (dllen > 1)
if (dllen > 1 || !mux || !mux->mm_pmt_06_ac3)
break;
/* fall through to SCT_AC3 */
case 0x81:

View file

@ -72,6 +72,7 @@ const idclass_t linuxdvb_adapter_class =
{
.ic_class = "linuxdvb_adapter",
.ic_caption = "LinuxDVB Adapter",
.ic_event = "linuxdvb_adapter",
.ic_save = linuxdvb_adapter_class_save,
.ic_get_childs = linuxdvb_adapter_class_get_childs,
.ic_get_title = linuxdvb_adapter_class_get_title,

View file

@ -18,9 +18,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Open things:
* - TODO: collision dectection
* when a en50494-command wasn't executed succesful, retry.
* delay time is easly random, but in standard is special (complicated) way described (cap. 8).
* - TODO: collision detection
* * compare transport-stream-id from stream with id in config
* * check continuity of the pcr-counter
* * when one point is given -> retry
* * delay time is easily random, but in standard is special (complicated) way described (cap. 8).
*/
#include "tvheadend.h"
@ -39,7 +41,7 @@
#define LINUXDVB_EN50494_NOPIN 256
#define LINUXDVB_EN50494_FRAME 0xE0
/* adresses 0x00, 0x10 and 0x11 are possible */
/* addresses 0x00, 0x10 and 0x11 are possible */
#define LINUXDVB_EN50494_ADDRESS 0x10
#define LINUXDVB_EN50494_CMD_NORMAL 0x5A
@ -162,7 +164,7 @@ linuxdvb_en50494_tune
linuxdvb_en50494_t *le = (linuxdvb_en50494_t*) ld;
linuxdvb_lnb_t *lnb = sc->lse_lnb;
/* band & polarisation */
/* band & polarization */
uint8_t pol = lnb->lnb_pol(lnb, lm);
uint8_t band = lnb->lnb_band(lnb, lm);
uint32_t freq = lnb->lnb_freq(lnb, lm);
@ -180,21 +182,29 @@ linuxdvb_en50494_tune
/* 2 data fields (16bit) */
uint8_t data1, data2;
data1 = (le->le_id & 7) << 5; /* 3bit user-band */
data1 |= (le->le_position & 1) << 4; /* 1bit position (satelitte A(0)/B(1)) */
data1 |= (pol & 1) << 3; /* 1bit polarisation v(0)/h(1) */
data1 |= (le->le_position & 1) << 4; /* 1bit position (satellite A(0)/B(1)) */
data1 |= (pol & 1) << 3; /* 1bit polarization v(0)/h(1) */
data1 |= (band & 1) << 2; /* 1bit band lower(0)/upper(1) */
data1 |= (t >> 8) & 3; /* 2bit transponder value bit 1-2 */
data2 = t & 0xFF; /* 8bit transponder value bit 3-10 */
tvhdebug("en50494",
"lnb=%i id=%i freq=%i pin=%i v/h=%i l/u=%i f=%i, data=0x%02X%02X",
le->le_position, le->le_id, le->le_frequency, le->le_pin, pol,
band, freq, data1, data2);
pthread_mutex_lock(&linuxdvb_en50494_lock);
/* wait until no other thread is setting up switch.
* when an other thread was blocking, waiting 20ms.
*/
if (pthread_mutex_trylock(&linuxdvb_en50494_lock) != 0) {
if (pthread_mutex_lock(&linuxdvb_en50494_lock) != 0) {
tvherror("en50494","failed to lock for tuning");
return -1;
}
usleep(20000);
}
/* setup en50494 switch */
for (i = 0; i <= sc->lse_parent->ls_diseqc_repeats; i++) {
/* to avoid repeated collision, wait a random time (5-25ms) */
/* to avoid repeated collision, wait a random time 68-118
* 67,5 is the typical diseqc-time */
if (i != 0) {
int ms = rand()%20 + 5;
int ms = rand()%50 + 68;
usleep(ms*1000);
}
@ -207,6 +217,10 @@ linuxdvb_en50494_tune
usleep(15000); /* standard: 4ms < x < 22ms */
/* send tune command (with/without pin) */
tvhdebug("en50494",
"lnb=%i id=%i freq=%i pin=%i v/h=%i l/u=%i f=%i, data=0x%02X%02X",
le->le_position, le->le_id, le->le_frequency, le->le_pin, pol,
band, freq, data1, data2);
if (le->le_pin != LINUXDVB_EN50494_NOPIN) {
ret = linuxdvb_diseqc_send(fd,
LINUXDVB_EN50494_FRAME,
@ -273,7 +287,7 @@ linuxdvb_en50494_create0
return NULL;
if (port > 1) {
tvherror("en50494", "only 2 ports/positions are posible. given %i", port);
tvherror("en50494", "only 2 ports/positions are possible. given %i", port);
port = 0;
}

View file

@ -289,13 +289,14 @@ linuxdvb_frontend_stop_mux
lfe->lfe_ready = 0;
lfe->lfe_locked = 0;
lfe->lfe_status = 0;
assert(lfe->lfe_in_setup == 0);
/* Ensure it won't happen immediately */
gtimer_arm(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 2);
if (lfe->lfe_satconf)
linuxdvb_satconf_post_stop_mux(lfe->lfe_satconf);
lfe->lfe_in_setup = 0;
}
static int

View file

@ -105,6 +105,10 @@ static int
linuxdvb_lnb_standard_tune
( linuxdvb_diseqc_t *ld, dvb_mux_t *lm, linuxdvb_satconf_ele_t *ls, int fd )
{
/* en50494 does not use the voltage tune. this is happend in the switch */
if (ls->lse_en50494)
return 0;
int pol = linuxdvb_lnb_standard_pol((linuxdvb_lnb_t*)ld, lm);
return linuxdvb_diseqc_set_volt(fd, pol);
}

View file

@ -211,6 +211,7 @@ const idclass_t linuxdvb_satconf_class =
{
.ic_class = "linuxdvb_satconf",
.ic_caption = "DVB-S Satconf",
.ic_event = "linuxdvb_satconf",
.ic_get_title = linuxdvb_satconf_class_get_title,
.ic_save = linuxdvb_satconf_class_save,
.ic_properties = (const property_t[]) {
@ -632,10 +633,12 @@ linuxdvb_satconf_ele_tune ( linuxdvb_satconf_ele_t *lse )
// TODO: really need to understand whether or not we need to pre configure
// and/or re-affirm the switch
/* Disable tone */
if (ioctl(lfe->lfe_fe_fd, FE_SET_TONE, SEC_TONE_OFF)) {
tvherror("diseqc", "failed to disable tone");
return -1;
/* Disable tone (en50494 don't use tone) */
if (!lse->lse_en50494) {
if (ioctl(lfe->lfe_fe_fd, FE_SET_TONE, SEC_TONE_OFF)) {
tvherror("diseqc", "failed to disable tone");
return -1;
}
}
/* Diseqc */
@ -659,14 +662,16 @@ linuxdvb_satconf_ele_tune ( linuxdvb_satconf_ele_t *lse )
&lse->lse_parent->ls_orbital_pos,
&lse->lse_parent->ls_orbital_dir);
/* Set the tone */
b = lse->lse_lnb->lnb_band(lse->lse_lnb, lm);
tvhtrace("disqec", "set diseqc tone %s", b ? "on" : "off");
if (ioctl(lfe->lfe_fe_fd, FE_SET_TONE, b ? SEC_TONE_ON : SEC_TONE_OFF)) {
tvherror("diseqc", "failed to set diseqc tone (e=%s)", strerror(errno));
return -1;
/* Set the tone (en50494 don't use tone) */
if (!lse->lse_en50494) {
b = lse->lse_lnb->lnb_band(lse->lse_lnb, lm);
tvhtrace("disqec", "set diseqc tone %s", b ? "on" : "off");
if (ioctl(lfe->lfe_fe_fd, FE_SET_TONE, b ? SEC_TONE_ON : SEC_TONE_OFF)) {
tvherror("diseqc", "failed to set diseqc tone (e=%s)", strerror(errno));
return -1;
}
usleep(20000); // Allow LNB to settle before tuning
}
usleep(20000); // Allow LNB to settle before tuning
/* Frontend */
/* use en50494 tuning frequency, if needed (not channel frequency) */
@ -700,13 +705,16 @@ linuxdvb_satconf_start_mux
// Note: basically this ensures the tuning params are acceptable
// for the FE, so that if they're not we don't have to wait
// for things like rotors and switches
// the en50494 have to skip this test
if (!lse->lse_lnb)
return SM_CODE_TUNING_FAILED;
f = lse->lse_lnb->lnb_freq(lse->lse_lnb, lm);
if (f == (uint32_t)-1)
return SM_CODE_TUNING_FAILED;
r = linuxdvb_frontend_tune0(lfe, mmi, f);
if (r) return r;
if (!lse->lse_en50494) {
r = linuxdvb_frontend_tune0(lfe, mmi, f);
if (r) return r;
}
/* Diseqc */
ls->ls_mmi = mmi;
@ -1020,6 +1028,7 @@ const idclass_t linuxdvb_satconf_ele_class =
{
.ic_class = "linuxdvb_satconf_ele",
.ic_caption = "Satconf",
.ic_event = "linuxdvb_satconf_ele",
.ic_get_title = linuxdvb_satconf_ele_class_get_title,
.ic_get_childs = linuxdvb_satconf_ele_class_get_childs,
.ic_save = linuxdvb_satconf_ele_class_save,
@ -1205,6 +1214,7 @@ const idclass_t linuxdvb_diseqc_class =
{
.ic_class = "linuxdvb_diseqc",
.ic_caption = "DiseqC",
.ic_event = "linuxdvb_diseqc",
.ic_get_title = linuxdvb_diseqc_class_get_title,
.ic_save = linuxdvb_diseqc_class_save,
};

View file

@ -142,6 +142,7 @@ const idclass_t mpegts_input_class =
{
.ic_class = "mpegts_input",
.ic_caption = "MPEGTS Input",
.ic_event = "mpegts_input",
.ic_get_title = mpegts_input_class_get_title,
.ic_properties = (const property_t[]){
{
@ -851,6 +852,7 @@ mpegts_input_table_thread ( void *aux )
{
mpegts_table_feed_t *mtf;
mpegts_input_t *mi = aux;
int i;
pthread_mutex_lock(&mi->mi_output_lock);
while (mi->mi_running) {
@ -866,7 +868,18 @@ mpegts_input_table_thread ( void *aux )
/* Process */
if (mtf->mtf_mux) {
pthread_mutex_lock(&global_lock);
mpegts_input_table_dispatch(mtf->mtf_mux, mtf->mtf_tsb);
if (mi->mi_destroyed_muxes) {
for (i = 0; i < mi->mi_destroyed_muxes_count; i++)
if (mtf->mtf_mux == mi->mi_destroyed_muxes[i])
goto clean;
mpegts_input_table_dispatch(mtf->mtf_mux, mtf->mtf_tsb);
clean:
free(mi->mi_destroyed_muxes);
mi->mi_destroyed_muxes = NULL;
mi->mi_destroyed_muxes_count = 0;
} else {
mpegts_input_table_dispatch(mtf->mtf_mux, mtf->mtf_tsb);
}
pthread_mutex_unlock(&global_lock);
}
@ -892,6 +905,8 @@ mpegts_input_flush_mux
mpegts_table_feed_t *mtf;
mpegts_packet_t *mp;
lock_assert(&global_lock);
// Note: to avoid long delays in here, rather than actually
// remove things from the Q, we simply invalidate by clearing
// the mux pointer and allow the threads to deal with the deletion
@ -910,6 +925,10 @@ mpegts_input_flush_mux
if (mtf->mtf_mux == mm)
mtf->mtf_mux = NULL;
}
mi->mi_destroyed_muxes = realloc(mi->mi_destroyed_muxes,
(mi->mi_destroyed_muxes_count + 1) *
sizeof(mpegts_mux_t *));
mi->mi_destroyed_muxes[mi->mi_destroyed_muxes_count++] = mm;
pthread_mutex_unlock(&mi->mi_output_lock);
}
@ -1136,6 +1155,7 @@ mpegts_input_delete ( mpegts_input_t *mi, int delconf )
pthread_mutex_destroy(&mi->mi_output_lock);
pthread_cond_destroy(&mi->mi_table_cond);
free(mi->mi_name);
free(mi->mi_destroyed_muxes);
free(mi);
}

View file

@ -303,13 +303,13 @@ mpegts_mux_epg_list ( void *o )
{ "Disable", MM_EPG_DISABLE },
{ "Enable (auto)", MM_EPG_ENABLE },
{ "Force (auto)", MM_EPG_FORCE },
{ "Force EIT", MM_EPG_FORCE_EIT },
{ "Force UK Freesat", MM_EPG_FORCE_UK_FREESAT },
{ "Force UK Freeview", MM_EPG_FORCE_UK_FREEVIEW },
{ "Force Viasat Baltic", MM_EPG_FORCE_VIASAT_BALTIC },
{ "Force OpenTV Sky UK", MM_EPG_FORCE_OPENTV_SKY_UK },
{ "Force OpenTV Sky Italia", MM_EPG_FORCE_OPENTV_SKY_ITALIA },
{ "Force OpenTV Sky Ausat", MM_EPG_FORCE_OPENTV_SKY_AUSAT },
{ "Only EIT", MM_EPG_ONLY_EIT },
{ "Only UK Freesat", MM_EPG_ONLY_UK_FREESAT },
{ "Only UK Freeview", MM_EPG_ONLY_UK_FREEVIEW },
{ "Only Viasat Baltic", MM_EPG_ONLY_VIASAT_BALTIC },
{ "Only OpenTV Sky UK", MM_EPG_ONLY_OPENTV_SKY_UK },
{ "Only OpenTV Sky Italia", MM_EPG_ONLY_OPENTV_SKY_ITALIA },
{ "Only OpenTV Sky Ausat", MM_EPG_ONLY_OPENTV_SKY_AUSAT },
};
return strtab2htsmsg(tab);
}
@ -406,6 +406,13 @@ const idclass_t mpegts_mux_class =
.opts = PO_RDONLY | PO_NOSAVE,
.get = mpegts_mux_class_get_num_svc,
},
{
.type = PT_BOOL,
.id = "pmt_06_ac3",
.name = "PMT Descriptor 0x06 = AC-3",
.off = offsetof(mpegts_mux_t, mm_pmt_06_ac3),
.opts = PO_ADVANCED,
},
{}
}
};

View file

@ -27,8 +27,8 @@
static void
mpegts_network_scan_notify ( mpegts_mux_t *mm )
{
idnode_updated(&mm->mm_id);
idnode_updated(&mm->mm_network->mn_id);
idnode_notify_simple(&mm->mm_id);
idnode_notify_simple(&mm->mm_network->mn_id);
}
static int

View file

@ -20,6 +20,7 @@
#include <assert.h>
#include "service.h"
#include "channels.h"
#include "input.h"
#include "settings.h"
#include "dvb_charset.h"
@ -101,6 +102,13 @@ const idclass_t mpegts_service_class =
.opts = PO_RDONLY,
.off = offsetof(mpegts_service_t, s_dvb_channel_num),
},
{
.type = PT_U16,
.id = "lcn_minor",
.name = "Local Channel Minor",
.opts = PO_RDONLY,
.off = offsetof(mpegts_service_t, s_dvb_channel_minor),
},
{
.type = PT_U16,
.id = "lcn2",
@ -364,12 +372,13 @@ mpegts_service_grace_period(service_t *t)
/*
* Channel number
*/
static int
static int64_t
mpegts_service_channel_number ( service_t *s )
{
int r = ((mpegts_service_t*)s)->s_dvb_channel_num;
int r = ((mpegts_service_t*)s)->s_dvb_channel_num * CHANNEL_SPLIT +
((mpegts_service_t*)s)->s_dvb_channel_minor;
if (r <= 0)
r = ((mpegts_service_t*)s)->s_dvb_opentv_chnum;
r = ((mpegts_service_t*)s)->s_dvb_opentv_chnum * CHANNEL_SPLIT;
return r;
}
@ -423,7 +432,9 @@ mpegts_service_create0
{
int r;
char buf[256];
service_create0((service_t*)s, class, uuid, S_MPEG_TS, conf);
if (service_create0((service_t*)s, class, uuid, S_MPEG_TS, conf) == NULL)
return NULL;
/* Create */
sbuf_init(&s->s_tsbuf);
@ -457,8 +468,8 @@ mpegts_service_create0
tvhlog(LOG_DEBUG, "mpegts", "%s - add service %04X %s", buf, s->s_dvb_service_id, s->s_dvb_svcname);
/* Notification */
idnode_updated(&mm->mm_id);
idnode_updated(&mm->mm_network->mn_id);
idnode_notify_simple(&mm->mm_id);
idnode_notify_simple(&mm->mm_network->mn_id);
return s;
}

View file

@ -169,6 +169,7 @@ const idclass_t satip_satconf_class =
{
.ic_class = "satip_satconf",
.ic_caption = "Satconf",
.ic_event = "satip_satconf",
.ic_get_title = satip_satconf_class_get_title,
.ic_save = satip_satconf_class_save,
.ic_properties = (const property_t[]) {

View file

@ -50,6 +50,8 @@ lang_str_t *lang_str_create ( void )
void lang_str_destroy ( lang_str_t *ls )
{
lang_str_ele_t *e;
if (ls == NULL)
return;
while ((e = RB_FIRST(ls))) {
if (e->str) free(e->str);
RB_REMOVE(ls, e, link);
@ -158,38 +160,95 @@ int lang_str_append
return _lang_str_add(ls, str, lang, 0, 1);
}
/* Serialize */
void lang_str_serialize ( lang_str_t *ls, htsmsg_t *m, const char *f )
/* Serialize map */
htsmsg_t *lang_str_serialize_map ( lang_str_t *ls )
{
lang_str_ele_t *e;
if (!ls) return;
if (!ls) return NULL;
htsmsg_t *a = htsmsg_create_map();
RB_FOREACH(e, ls, link) {
htsmsg_add_str(a, e->lang, e->str);
}
htsmsg_add_msg(m, f, a);
return a;
}
/* Serialize */
void lang_str_serialize ( lang_str_t *ls, htsmsg_t *m, const char *f )
{
if (!ls) return;
htsmsg_add_msg(m, f, lang_str_serialize_map(ls));
}
/* De-serialize map */
lang_str_t *lang_str_deserialize_map ( htsmsg_t *map )
{
lang_str_t *ret = lang_str_create();
htsmsg_field_t *f;
const char *str;
HTSMSG_FOREACH(f, map) {
if ((str = htsmsg_field_get_string(f))) {
lang_str_add(ret, str, f->hmf_name, 0);
}
}
return ret;
}
/* De-serialize */
lang_str_t *lang_str_deserialize ( htsmsg_t *m, const char *n )
{
lang_str_t *ret = NULL;
htsmsg_t *a;
htsmsg_field_t *f;
const char *str;
if ((a = htsmsg_get_map(m, n))) {
ret = lang_str_create();
HTSMSG_FOREACH(f, a) {
if ((str = htsmsg_field_get_string(f))) {
lang_str_add(ret, str, f->hmf_name, 0);
}
}
return lang_str_deserialize_map(a);
} else if ((str = htsmsg_get_str(m, n))) {
ret = lang_str_create();
lang_str_t *ret = lang_str_create();
lang_str_add(ret, str, NULL, 0);
return ret;
}
return ret;
return NULL;
}
/* Compare */
int lang_str_compare( lang_str_t *ls1, lang_str_t *ls2 )
{
lang_str_ele_t *e;
const char *s1, *s2;
int r;
if (ls1 == NULL && ls2)
return -1;
if (ls2 == NULL && ls1)
return 1;
if (ls1 == ls2)
return 0;
/* Note: may be optimized to not check languages twice */
RB_FOREACH(e, ls1, link) {
s1 = lang_str_get(ls1, e->lang);
s2 = lang_str_get(ls2, e->lang);
if (s1 == NULL && s2 != NULL)
return -1;
if (s2 == NULL && s1 != NULL)
return 1;
if (s1 == NULL || s2 == NULL)
continue;
r = strcmp(s1, s2);
if (r) return r;
}
RB_FOREACH(e, ls2, link) {
s1 = lang_str_get(ls1, e->lang);
s2 = lang_str_get(ls2, e->lang);
if (s1 == NULL && s2 != NULL)
return -1;
if (s2 == NULL && s1 != NULL)
return 1;
if (s1 == NULL || s2 == NULL)
continue;
r = strcmp(s1, s2);
if (r) return r;
}
return 0;
}
void lang_str_done( void )

View file

@ -47,11 +47,18 @@ int lang_str_append
( lang_str_t *ls, const char *str, const char *lang );
/* Serialize/Deserialize */
htsmsg_t *lang_str_serialize_map
( lang_str_t *ls );
void lang_str_serialize
( lang_str_t *ls, htsmsg_t *msg, const char *f );
lang_str_t *lang_str_deserialize_map
( htsmsg_t *map );
lang_str_t *lang_str_deserialize
( htsmsg_t *m, const char *f );
/* Compare */
int lang_str_compare ( lang_str_t *ls1, lang_str_t *ls2 );
/* Init/Done */
void lang_str_done( void );

View file

@ -484,7 +484,8 @@ main(int argc, char **argv)
opt_dump = 0,
opt_xspf = 0,
opt_dbus = 0,
opt_dbus_session = 0;
opt_dbus_session = 0,
opt_nobackup = 0;
const char *opt_config = NULL,
*opt_user = NULL,
*opt_group = NULL,
@ -507,6 +508,7 @@ main(int argc, char **argv)
{ 0, NULL, "Service Configuration", OPT_BOOL, NULL },
{ 'c', "config", "Alternate config path", OPT_STR, &opt_config },
{ 'B', "nobackup", "Do not backup config tree at upgrade", OPT_BOOL, &opt_nobackup },
{ 'f', "fork", "Fork and run as daemon", OPT_BOOL, &opt_fork },
{ 'u', "user", "Run as user", OPT_STR, &opt_user },
{ 'g', "group", "Run as group", OPT_STR, &opt_group },
@ -787,7 +789,7 @@ main(int argc, char **argv)
/* Initialise configuration */
uuid_init();
idnode_init();
config_init(opt_config);
config_init(opt_config, opt_nobackup == 0);
/**
* Initialize subsystems
@ -821,6 +823,8 @@ main(int argc, char **argv)
subscription_init();
dvr_config_init();
access_init(opt_firstrun, opt_noacl);
#if ENABLE_TIMESHIFT

View file

@ -21,9 +21,6 @@
#include "htsmsg.h"
#define MC_REWRITE_PAT 0x0001
#define MC_REWRITE_PMT 0x0002
#define MC_IS_EOS_ERROR(e) ((e) == EPIPE || (e) == ECONNRESET)
typedef enum {
@ -47,8 +44,9 @@ typedef enum {
/* Muxer configuration used when creating a muxer. */
typedef struct muxer_config {
int m_flags;
muxer_cache_type_t m_cache;
int m_rewrite_pat;
int m_rewrite_pmt;
int m_cache;
/*
* directory_permissions should really be in dvr.h as it's not really needed for the muxer

View file

@ -316,7 +316,7 @@ pass_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss)
pm->pm_pmt_pid = ss->ss_pmt_pid;
pm->pm_service_id = ss->ss_service_id;
if (pm->m_config.m_flags & MC_REWRITE_PMT) {
if (pm->m_config.m_rewrite_pmt) {
pm->pm_pmt = realloc(pm->pm_pmt, 188);
memset(pm->pm_pmt, 0xff, 188);
pm->pm_pmt[0] = 0x47;
@ -433,16 +433,15 @@ pass_muxer_write_ts(muxer_t *m, pktbuf_t *pb)
size_t len = pb->pb_size;
/* Rewrite PAT/PMT in operation */
if (pm->m_config.m_flags & (MC_REWRITE_PAT | MC_REWRITE_PMT)) {
if (pm->m_config.m_rewrite_pat || pm->m_config.m_rewrite_pmt) {
tsb = pb->pb_data;
len = 0;
while (tsb < pb->pb_data + pb->pb_size) {
int pid = (tsb[1] & 0x1f) << 8 | tsb[2];
/* Process */
if ( ((pm->m_config.m_flags & MC_REWRITE_PAT) && (pid == 0)) ||
((pm->m_config.m_flags & MC_REWRITE_PMT) &&
(pid == pm->pm_pmt_pid)) ) {
if ( (pm->m_config.m_rewrite_pat && (pid == 0)) ||
(pm->m_config.m_rewrite_pmt && (pid == pm->pm_pmt_pid)) ) {
/* Flush */
if (len)
@ -458,7 +457,7 @@ pass_muxer_write_ts(muxer_t *m, pktbuf_t *pb)
e = pass_muxer_rewrite_pat(pm, tmp);
if (e < 0) {
tvherror("pass", "PAT rewrite failed, disabling");
pm->m_config.m_flags &= ~MC_REWRITE_PAT;
pm->m_config.m_rewrite_pat = 0;
}
if (e)
pass_muxer_write(m, tmp, 188);

View file

@ -652,6 +652,7 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
char datestr[64], ctype[100];
const epg_genre_t *eg = NULL;
epg_genre_t eg0;
struct tm tm;
localtime_r(de ? &de->de_start : &ebc->start, &tm);
epg_episode_t *ee = NULL;
@ -677,8 +678,10 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
addtag(q, build_tag_string("ORIGINAL_MEDIA_TYPE", "TV", NULL, 0, NULL));
if(de && de->de_content_type.code) {
eg = &de->de_content_type;
if(de && de->de_content_type) {
memset(&eg0, 0, sizeof(eg0));
eg0.code = de->de_content_type;
eg = &eg0;
} else if (ee) {
eg = LIST_FIRST(&ee->genre);
}

View file

@ -22,6 +22,7 @@
#include "tvheadend.h"
#include "prop.h"
#include "lang_str.h"
/* **************************************************************************
* Utilities
@ -31,12 +32,16 @@
*
*/
const static struct strtab typetab[] = {
{ "bool", PT_BOOL },
{ "int", PT_INT },
{ "str", PT_STR },
{ "u16", PT_U16 },
{ "u32", PT_U32 },
{ "dbl", PT_DBL },
{ "bool", PT_BOOL },
{ "int", PT_INT },
{ "str", PT_STR },
{ "u16", PT_U16 },
{ "u32", PT_U32 },
{ "s64", PT_S64 },
{ "dbl", PT_DBL },
{ "time", PT_TIME },
{ "langstr", PT_LANGSTR },
{ "perm", PT_PERM },
};
@ -71,6 +76,7 @@ prop_write_values
int64_t s64;
uint32_t u32;
uint16_t u16;
time_t tm;
#define PROP_UPDATE(v, t)\
new = &v;\
if (!p->set && (*((t*)cur) != *((t*)new))) {\
@ -81,13 +87,18 @@ prop_write_values
if (!pl) return 0;
for (p = pl; p->id; p++) {
if (p->type == PT_NONE) continue;
f = htsmsg_field_find(m, p->id);
if (!f) continue;
/* Ignore */
if(p->opts & optmask) continue;
u32 = p->get_opts ? p->get_opts(obj) : p->opts;
if(u32 & optmask) continue;
/* Sanity check */
assert(p->set || p->off);
/* Write */
save = 0;
@ -122,11 +133,35 @@ prop_write_values
break;
}
case PT_U32: {
if (htsmsg_field_get_u32(f, &u32))
continue;
if (p->intsplit) {
char *s;
if (!(new = htsmsg_field_get_str(f)))
continue;
u32 = atol(new) * p->intsplit;
if ((s = strchr(new, '.')) != NULL)
u32 += (atol(s + 1) % p->intsplit);
} else {
if (htsmsg_field_get_u32(f, &u32))
continue;
}
PROP_UPDATE(u32, uint32_t);
break;
}
case PT_S64: {
if (p->intsplit) {
char *s;
if (!(new = htsmsg_field_get_str(f)))
continue;
s64 = (int64_t)atol(new) * p->intsplit;
if ((s = strchr(new, '.')) != NULL)
s64 += (atol(s + 1) % p->intsplit);
} else {
if (htsmsg_field_get_s64(f, &s64))
continue;
}
PROP_UPDATE(s64, int64_t);
break;
}
case PT_DBL: {
if (htsmsg_field_get_dbl(f, &dbl))
continue;
@ -144,6 +179,38 @@ prop_write_values
}
break;
}
case PT_TIME: {
if (htsmsg_field_get_s64(f, &s64))
continue;
tm = s64;
PROP_UPDATE(tm, time_t);
break;
}
case PT_LANGSTR: {
lang_str_t **lstr1 = cur;
lang_str_t *lstr2;
new = htsmsg_field_get_map(f);
if (!new)
continue;
if (!p->set) {
lstr2 = lang_str_deserialize_map((htsmsg_t *)new);
if (lang_str_compare(*lstr1, lstr2)) {
lang_str_destroy(*lstr1);
*lstr1 = lstr2;
save = 1;
} else {
lang_str_destroy(lstr2);
}
}
break;
}
case PT_PERM: {
if (!(new = htsmsg_field_get_str(f)))
continue;
u32 = (int)strtol(new,NULL,0);
PROP_UPDATE(u32, uint32_t);
break;
}
case PT_NONE:
break;
}
@ -175,19 +242,20 @@ prop_write_values
*/
static void
prop_read_value
(void *obj, const property_t *p, htsmsg_t *m, const char *name,
int optmask, htsmsg_t *inc)
(void *obj, const property_t *p, htsmsg_t *m, const char *name, int optmask)
{
const char *s;
const void *val = obj + p->off;
uint32_t u32;
char buf[24];
/* Ignore */
if (p->opts & optmask) return;
u32 = p->get_opts ? p->get_opts(obj) : p->opts;
if (u32 & optmask) return;
if (p->type == PT_NONE) return;
/* Ignore */
if (inc && !htsmsg_get_u32_or_default(inc, p->id, 0))
return;
/* Sanity check */
assert(p->get || p->off);
/* Get method */
if (!(optmask & PO_USERAW) || !p->off)
@ -207,12 +275,33 @@ prop_read_value
case PT_INT:
htsmsg_add_s64(m, name, *(int *)val);
break;
case PT_U32:
htsmsg_add_u32(m, name, *(uint32_t *)val);
break;
case PT_U16:
htsmsg_add_u32(m, name, *(uint16_t *)val);
break;
case PT_U32:
if (p->intsplit) {
uint32_t maj = *(int64_t *)val / p->intsplit;
uint32_t min = *(int64_t *)val % p->intsplit;
if (min) {
snprintf(buf, sizeof(buf), "%u.%u", (unsigned int)maj, (unsigned int)min);
htsmsg_add_str(m, name, buf);
} else
htsmsg_add_s64(m, name, maj);
} else
htsmsg_add_u32(m, name, *(uint32_t *)val);
break;
case PT_S64:
if (p->intsplit) {
int64_t maj = *(int64_t *)val / p->intsplit;
int64_t min = *(int64_t *)val % p->intsplit;
if (min) {
snprintf(buf, sizeof(buf), "%lu.%lu", (unsigned long)maj, (unsigned long)min);
htsmsg_add_str(m, name, buf);
} else
htsmsg_add_s64(m, name, maj);
} else
htsmsg_add_s64(m, name, *(int64_t *)val);
break;
case PT_STR:
if ((s = *(const char **)val))
htsmsg_add_str(m, name, s);
@ -220,6 +309,16 @@ prop_read_value
case PT_DBL:
htsmsg_add_dbl(m, name, *(double*)val);
break;
case PT_TIME:
htsmsg_add_s64(m, name, *(time_t *)val);
break;
case PT_LANGSTR:
lang_str_serialize(*(lang_str_t **)val, m, name);
break;
case PT_PERM:
snprintf(buf, sizeof(buf), "%04o", *(uint32_t *)val);
htsmsg_add_str(m, name, buf);
break;
case PT_NONE:
break;
}
@ -231,12 +330,141 @@ prop_read_value
*/
void
prop_read_values
(void *obj, const property_t *pl, htsmsg_t *m, int optmask, htsmsg_t *inc)
(void *obj, const property_t *pl, htsmsg_t *m, htsmsg_t *list, int optmask)
{
if(pl == NULL)
return;
for (; pl->id; pl++)
prop_read_value(obj, pl, m, pl->id, optmask, inc);
if(list == NULL) {
for (; pl->id; pl++)
prop_read_value(obj, pl, m, pl->id, optmask);
} else {
const property_t *p;
htsmsg_field_t *f;
int b;
HTSMSG_FOREACH(f, list) {
if (!htsmsg_field_get_bool(f, &b) && b > 0) {
p = prop_find(pl, f->hmf_name);
if (p)
prop_read_value(obj, p, m, p->id, optmask);
}
}
}
}
/**
*
*/
static void
prop_serialize_value
(void *obj, const property_t *pl, htsmsg_t *msg, int optmask)
{
htsmsg_field_t *f;
char buf[16];
uint32_t opts;
/* Remove parent */
// TODO: this is really horrible and inefficient!
HTSMSG_FOREACH(f, msg) {
htsmsg_t *t = htsmsg_field_get_map(f);
const char *str;
if (t && (str = htsmsg_get_str(t, "id"))) {
if (!strcmp(str, pl->id)) {
htsmsg_field_destroy(msg, f);
break;
}
}
}
htsmsg_t *m = htsmsg_create_map();
/* ID / type */
htsmsg_add_str(m, "id", pl->id);
htsmsg_add_str(m, "type", val2str(pl->type, typetab) ?: "none");
/* Skip - special blocker */
if (pl->type == PT_NONE) {
htsmsg_add_msg(msg, NULL, m);
return;
}
/* Metadata */
htsmsg_add_str(m, "caption", pl->name);
if (pl->islist)
htsmsg_add_u32(m, "list", 1);
/* Default */
// TODO: currently no support for list defaults
switch (pl->type) {
case PT_BOOL:
htsmsg_add_bool(m, "default", pl->def.i);
break;
case PT_INT:
htsmsg_add_s32(m, "default", pl->def.i);
break;
case PT_U16:
htsmsg_add_u32(m, "default", pl->def.u16);
break;
case PT_U32:
htsmsg_add_u32(m, "default", pl->def.u32);
break;
case PT_S64:
htsmsg_add_s64(m, "default", pl->def.s64);
break;
case PT_DBL:
htsmsg_add_dbl(m, "default", pl->def.d);
break;
case PT_STR:
htsmsg_add_str(m, "default", pl->def.s ?: "");
break;
case PT_TIME:
htsmsg_add_s64(m, "default", pl->def.tm);
break;
case PT_LANGSTR:
/* TODO? */
break;
case PT_PERM:
snprintf(buf, sizeof(buf), "%04o", pl->def.u32);
htsmsg_add_str(m, "default", buf);
break;
case PT_NONE:
break;
}
/* Options */
opts = pl->get_opts ? pl->get_opts(obj) : pl->opts;
if (opts & PO_RDONLY)
htsmsg_add_bool(m, "rdonly", 1);
if (opts & PO_NOSAVE)
htsmsg_add_bool(m, "nosave", 1);
if (opts & PO_WRONCE)
htsmsg_add_bool(m, "wronce", 1);
if (opts & PO_ADVANCED)
htsmsg_add_bool(m, "advanced", 1);
if (opts & PO_HIDDEN)
htsmsg_add_bool(m, "hidden", 1);
if (opts & PO_PASSWORD)
htsmsg_add_bool(m, "password", 1);
if (opts & PO_DURATION)
htsmsg_add_bool(m, "duration", 1);
/* Enum list */
if (pl->list)
htsmsg_add_msg(m, "enum", pl->list(obj));
/* Visual group */
if (pl->group)
htsmsg_add_u32(m, "group", pl->group);
/* Split integer value */
if (pl->intsplit)
htsmsg_add_u32(m, "intsplit", pl->intsplit);
/* Data */
if (obj)
prop_read_value(obj, pl, m, "value", optmask);
htsmsg_add_msg(msg, NULL, m);
}
/**
@ -244,97 +472,25 @@ prop_read_values
*/
void
prop_serialize
(void *obj, const property_t *pl, htsmsg_t *msg, int optmask, htsmsg_t *inc)
(void *obj, const property_t *pl, htsmsg_t *msg, htsmsg_t *list, int optmask)
{
htsmsg_field_t *f;
if(pl == NULL)
return;
for(; pl->id; pl++) {
/* Remove parent */
// TODO: this is really horrible and inefficient!
HTSMSG_FOREACH(f, msg) {
htsmsg_t *t = htsmsg_field_get_map(f);
const char *str;
if (t && (str = htsmsg_get_str(t, "id"))) {
if (!strcmp(str, pl->id)) {
htsmsg_field_destroy(msg, f);
break;
}
if(list == NULL) {
for (; pl->id; pl++)
prop_serialize_value(obj, pl, msg, optmask);
} else {
const property_t *p;
htsmsg_field_t *f;
int b;
HTSMSG_FOREACH(f, list) {
if (!htsmsg_field_get_bool(f, &b) && b > 0) {
p = prop_find(pl, f->hmf_name);
if (p)
prop_serialize_value(obj, p, msg, optmask);
}
}
/* Ignore */
if (inc && !htsmsg_get_u32_or_default(inc, pl->id, 0))
continue;
htsmsg_t *m = htsmsg_create_map();
/* ID / type */
htsmsg_add_str(m, "id", pl->id);
htsmsg_add_str(m, "type", val2str(pl->type, typetab) ?: "none");
/* Skip - special blocker */
if (pl->type == PT_NONE) {
htsmsg_add_msg(msg, NULL, m);
continue;
}
/* Metadata */
htsmsg_add_str(m, "caption", pl->name);
if (pl->islist)
htsmsg_add_u32(m, "list", 1);
/* Default */
// TODO: currently no support for list defaults
switch (pl->type) {
case PT_BOOL:
htsmsg_add_bool(m, "default", pl->def.i);
break;
case PT_INT:
htsmsg_add_s32(m, "default", pl->def.i);
break;
case PT_U16:
htsmsg_add_u32(m, "default", pl->def.u16);
break;
case PT_U32:
htsmsg_add_u32(m, "default", pl->def.u32);
break;
case PT_DBL:
htsmsg_add_dbl(m, "default", pl->def.d);
break;
case PT_STR:
htsmsg_add_str(m, "default", pl->def.s ?: "");
break;
case PT_NONE:
break;
}
/* Options */
if (pl->opts & PO_RDONLY)
htsmsg_add_bool(m, "rdonly", 1);
if (pl->opts & PO_NOSAVE)
htsmsg_add_bool(m, "nosave", 1);
if (pl->opts & PO_WRONCE)
htsmsg_add_bool(m, "wronce", 1);
if (pl->opts & PO_ADVANCED)
htsmsg_add_bool(m, "advanced", 1);
if (pl->opts & PO_HIDDEN)
htsmsg_add_bool(m, "hidden", 1);
if (pl->opts & PO_PASSWORD)
htsmsg_add_bool(m, "password", 1);
/* Enum list */
if (pl->list)
htsmsg_add_msg(m, "enum", pl->list(obj));
/* Data */
if (obj)
prop_read_value(obj, pl, m, "value", optmask, NULL);
htsmsg_add_msg(msg, NULL, m);
}
}

View file

@ -34,21 +34,26 @@ typedef enum {
PT_INT,
PT_U16,
PT_U32,
PT_S64,
PT_DBL,
PT_TIME,
PT_LANGSTR,
PT_PERM, // like PT_U32 but with the special save
} prop_type_t;
/*
* Property options
*/
#define PO_NONE 0x00
#define PO_RDONLY 0x01 // Property is read-only
#define PO_NOSAVE 0x02 // Property is transient (not saved)
#define PO_WRONCE 0x04 // Property is write-once (i.e. on creation)
#define PO_ADVANCED 0x08 // Property is advanced
#define PO_HIDDEN 0x10 // Property is hidden (by default)
#define PO_USERAW 0x20 // Only save the RAW (off) value if it exists
#define PO_SORTKEY 0x40 // Sort using key (not display value)
#define PO_PASSWORD 0x80 // String is a password
#define PO_NONE 0x0000
#define PO_RDONLY 0x0001 // Property is read-only
#define PO_NOSAVE 0x0002 // Property is transient (not saved)
#define PO_WRONCE 0x0004 // Property is write-once (i.e. on creation)
#define PO_ADVANCED 0x0008 // Property is advanced
#define PO_HIDDEN 0x0010 // Property is hidden (by default)
#define PO_USERAW 0x0020 // Only save the RAW (off) value if it exists
#define PO_SORTKEY 0x0040 // Sort using key (not display value)
#define PO_PASSWORD 0x0080 // String is a password
#define PO_DURATION 0x0100 // For PT_TIME - differentiate between duration and datetime
/*
* Property definition
@ -57,9 +62,11 @@ typedef struct property {
const char *id; ///< Property Key
const char *name; ///< Textual description
prop_type_t type; ///< Type
int islist; ///< Is a list
uint8_t islist; ///< Is a list
uint8_t group; ///< Visual group ID (like ExtJS FieldSet)
size_t off; ///< Offset into object
int opts; ///< Options
uint32_t opts; ///< Options
uint32_t intsplit; ///< integer/remainder boundary
/* String based processing */
const void *(*get) (void *ptr);
@ -72,12 +79,17 @@ typedef struct property {
/* Default (for UI) */
union {
int i; // PT_BOOL/PT_INT
const char *s; // PR_STR
const char *s; // PT_STR
uint16_t u16; // PT_U16
uint32_t u32; // PR_U32
uint32_t u32; // PT_U32
int64_t s64; // PT_S64
double d; // PT_DBL
time_t tm; // PT_TIME
} def;
/* Extended options */
uint32_t (*get_opts) (void *ptr);
/* Notification callback */
void (*notify) (void *ptr);
@ -89,10 +101,10 @@ int prop_write_values
(void *obj, const property_t *pl, htsmsg_t *m, int optmask, htsmsg_t *updated);
void prop_read_values
(void *obj, const property_t *pl, htsmsg_t *m, int optmask, htsmsg_t *inc);
(void *obj, const property_t *pl, htsmsg_t *m, htsmsg_t *list, int optmask);
void prop_serialize
(void *obj, const property_t *pl, htsmsg_t *m, int optmask, htsmsg_t *inc);
(void *obj, const property_t *pl, htsmsg_t *m, htsmsg_t *list, int optmask);
#endif /* __TVH_PROP_H__ */

View file

@ -115,13 +115,10 @@ static htsmsg_t *
service_class_channel_enum
( void *obj )
{
htsmsg_t *p, *m = htsmsg_create_map();
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "type", "api");
htsmsg_add_str(m, "uri", "channel/list");
htsmsg_add_str(m, "event", "channel");
p = htsmsg_create_map();
htsmsg_add_u32(p, "enum", 1);
htsmsg_add_msg(m, "params", p);
return m;
}
@ -171,6 +168,7 @@ service_class_caid_get ( void *obj )
const idclass_t service_class = {
.ic_class = "service",
.ic_caption = "Service",
.ic_event = "service",
.ic_save = service_class_save,
.ic_get_title = service_class_get_title,
.ic_properties = (const property_t[]){
@ -805,7 +803,7 @@ service_destroy(service_t *t, int delconf)
service_unref(t);
}
static int
static int64_t
service_channel_number ( service_t *s )
{
return 0;
@ -1488,7 +1486,6 @@ service_instance_add(service_instance_list_t *sil,
si->si_s = s;
service_ref(s);
si->si_instance = instance;
si->si_weight = weight;
} else {
si->si_mark = 0;
if(si->si_prio == prio && si->si_weight == weight)
@ -1570,7 +1567,7 @@ service_get_full_channel_name ( service_t *s )
/*
* Get number for service
*/
int
int64_t
service_get_channel_number ( service_t *s )
{
if (s->s_channel_number) return s->s_channel_number(s);

View file

@ -292,7 +292,7 @@ typedef struct service {
/**
* Channel info
*/
int (*s_channel_number) (struct service *);
int64_t (*s_channel_number) (struct service *);
const char *(*s_channel_name) (struct service *);
const char *(*s_provider_name) (struct service *);
@ -554,6 +554,6 @@ void sort_elementary_streams(service_t *t);
const char *service_get_channel_name (service_t *s);
const char *service_get_full_channel_name (service_t *s);
int service_get_channel_number (service_t *s);
int64_t service_get_channel_number (service_t *s);
#endif // SERVICE_H__

View file

@ -184,21 +184,22 @@ hts_settings_save(htsmsg_t *record, const char *pathfmt, ...)
static htsmsg_t *
hts_settings_load_one(const char *filename)
{
ssize_t n;
ssize_t n, size;
char *mem;
fb_file *fp;
htsmsg_t *r = NULL;
/* Open */
if (!(fp = fb_open(filename, 1, 0))) return NULL;
size = fb_size(fp);
/* Load data */
mem = malloc(fb_size(fp)+1);
n = fb_read(fp, mem, fb_size(fp));
mem = malloc(size+1);
n = fb_read(fp, mem, size);
if (n >= 0) mem[n] = 0;
/* Decode */
if(n == fb_size(fp))
if(n == size)
r = htsmsg_json_deserialize(mem);
/* Close */

View file

@ -84,50 +84,56 @@ find_exec ( const char *name, char *out, size_t len )
}
/**
* The reaper is called once a second to finish of any pending spawns
* Reap one child
*/
void
spawn_reaper(void)
int
spawn_reap(char *stxt, size_t stxtlen)
{
pid_t pid;
int status;
char txt[100];
int status, res;
spawn_t *s;
while(1) {
pid = waitpid(-1, &status, WNOHANG);
if(pid < 1)
pid = waitpid(-1, &status, WNOHANG);
if(pid < 1)
return -EAGAIN;
pthread_mutex_lock(&spawn_mutex);
LIST_FOREACH(s, &spawns, link)
if(s->pid == pid)
break;
pthread_mutex_lock(&spawn_mutex);
LIST_FOREACH(s, &spawns, link)
if(s->pid == pid)
break;
if (WIFEXITED(status)) {
snprintf(txt, sizeof(txt),
"exited, status=%d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
snprintf(txt, sizeof(txt),
"killed by signal %d", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
snprintf(txt, sizeof(txt),
"stopped by signal %d", WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
snprintf(txt, sizeof(txt),
"continued");
} else {
snprintf(txt, sizeof(txt),
"unknown status");
}
if(s != NULL) {
LIST_REMOVE(s, link);
free((void *)s->name);
free(s);
}
pthread_mutex_unlock(&spawn_mutex);
res = -EIO;
if (WIFEXITED(status)) {
res = WEXITSTATUS(status);
if (stxt)
snprintf(stxt, stxtlen, "exited, status=%d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
snprintf(stxt, stxtlen, "killed by signal %d, "
"stopped by signal %d",
WTERMSIG(status),
WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
snprintf(stxt, stxtlen, "continued");
} else {
snprintf(stxt, stxtlen, "unknown status");
}
if(s != NULL) {
LIST_REMOVE(s, link);
free((void *)s->name);
free(s);
}
pthread_mutex_unlock(&spawn_mutex);
return res;
}
/**
* The reaper is called once a second to finish of any pending spawns
*/
void
spawn_reaper(void)
{
while (spawn_reap(NULL, 0) != -EAGAIN) ;
}

View file

@ -25,6 +25,8 @@ int spawn_and_store_stdout(const char *prog, char *argv[], char **outp);
int spawnv(const char *prog, char *argv[]);
int spawn_reap(char *stxt, size_t stxtlen);
void spawn_reaper(void);
#endif /* SPAWN_H */

View file

@ -90,6 +90,7 @@ subscription_link_service(th_subscription_t *s, service_t *t)
sm = streaming_msg_create_code(SMT_GRACE, s->ths_postpone + t->s_grace_delay);
streaming_pad_deliver(&t->s_streaming_pad, sm);
streaming_msg_free(sm);
if(s->ths_start_message != NULL && t->s_streaming_status & TSS_PACKETS) {

View file

@ -177,6 +177,7 @@ void gtimer_disarm(gtimer_t *gti);
/*
* List / Queue header declarations
*/
LIST_HEAD(access_entry_list, access_entry);
LIST_HEAD(th_subscription_list, th_subscription);
LIST_HEAD(dvr_config_list, dvr_config);
LIST_HEAD(dvr_entry_list, dvr_entry);

View file

@ -139,11 +139,13 @@ static void
comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb)
{
htsmsg_t *m = htsmsg_create_map();
const char *username = hc->hc_access ? (hc->hc_access->aa_username ?: "") : "";
htsmsg_add_str(m, "notificationClass", "accessUpdate");
htsmsg_add_u32(m, "dvr", !http_access_verify(hc, ACCESS_RECORDER));
htsmsg_add_u32(m, "admin", !http_access_verify(hc, ACCESS_ADMIN));
htsmsg_add_str(m, "username", username);
htsmsg_add_u32(m, "dvr", !http_access_verify(hc, ACCESS_RECORDER));
htsmsg_add_u32(m, "admin", !http_access_verify(hc, ACCESS_ADMIN));
if(cmb->cmb_messages == NULL)
cmb->cmb_messages = htsmsg_create_list();

View file

@ -153,10 +153,6 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/esfilter.js");
#if ENABLE_MPEGTS
extjs_load(hq, "static/app/mpegts.js");
#endif
extjs_load(hq, "static/app/iptv.js");
#if ENABLE_V4L
extjs_load(hq, "static/app/v4l.js");
#endif
#if ENABLE_TIMESHIFT
extjs_load(hq, "static/app/timeshift.js");
@ -256,7 +252,7 @@ page_about(http_connection_t *hc, const char *remain, void *opaque)
"<div class=\"about-title\">"
"HTS Tvheadend %s"
"</div><br>"
"&copy; 2006 - 2013 Andreas \303\226man, et al.<br><br>"
"&copy; 2006 - 2014 Andreas \303\226man, et al.<br><br>"
"<img src=\"docresources/tvheadendlogo.png\"><br>"
"<a href=\"https://tvheadend.org\">"
"https://tvheadend.org</a><br><br>"
@ -362,24 +358,6 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
* EPG Content Groups
*/
static int
extjs_ecglist(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *array;
out = htsmsg_create_map();
array = epg_genres_list_all(1, 0);
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;
}
/**
*
*/
@ -502,127 +480,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
static int
extjs_confignames(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *op = http_arg_get(&hc->hc_req_args, "op");
htsmsg_t *out, *array, *e;
dvr_config_t *cfg;
pthread_mutex_lock(&global_lock);
if(op != NULL && !strcmp(op, "list")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
if (http_access_verify(hc, ACCESS_RECORDER_ALL))
goto skip;
LIST_FOREACH(cfg, &dvrconfigs, config_link) {
e = htsmsg_create_map();
htsmsg_add_str(e, "identifier", cfg->dvr_config_name);
if (strlen(cfg->dvr_config_name) == 0)
htsmsg_add_str(e, "name", "(default)");
else
htsmsg_add_str(e, "name", cfg->dvr_config_name);
htsmsg_add_msg(array, NULL, e);
}
skip:
htsmsg_add_msg(out, "entries", array);
} 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 int
extjs_dvr_containers(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *op = http_arg_get(&hc->hc_req_args, "op");
htsmsg_t *out, *array;
pthread_mutex_lock(&global_lock);
if(op != NULL && !strcmp(op, "list")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
muxer_container_list(array);
htsmsg_add_msg(out, "entries", array);
} 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 int
extjs_dvr_caches(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *op = http_arg_get(&hc->hc_req_args, "op");
htsmsg_t *out, *array;
pthread_mutex_lock(&global_lock);
if(op != NULL && !strcmp(op, "list")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
muxer_cache_list(array);
htsmsg_add_msg(out, "entries", array);
} 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;
}
/**
*
*/
@ -728,8 +585,8 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
else
limit = 20; /* XXX */
if ((s = http_arg_get(&hc->hc_req_args, "contenttype"))) {
genre.code = atoi(s);
if ((s = http_arg_get(&hc->hc_req_args, "content_type"))) {
genre.code = atoi(s) * 16;
eg = &genre;
}
@ -788,7 +645,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
htsmsg_add_str(m, "serieslink", e->serieslink->uri);
if((eg = LIST_FIRST(&ee->genre))) {
htsmsg_add_u32(m, "contenttype", eg->code);
htsmsg_add_u32(m, "content_type", eg->code / 16);
}
dvr_entry_t *de;
@ -930,501 +787,6 @@ extjs_epgobject(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
static int
extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *op = http_arg_get(&hc->hc_req_args, "op");
htsmsg_t *out, *r;
dvr_entry_t *de;
const char *s;
int flags = 0;
dvr_config_t *cfg;
epg_broadcast_t *e;
char buffer[5]; // Permissions buffer: leading zero, three octal digits plus terminating null
if(op == NULL)
op = "loadSettings";
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_RECORDER)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
if(!strcmp(op, "recordEvent") || !strcmp(op, "recordSeries")) {
const char *config_name = http_arg_get(&hc->hc_req_args, "config_name");
s = http_arg_get(&hc->hc_req_args, "eventId");
if((e = epg_broadcast_find_by_id(atoi(s), NULL)) == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
if (http_access_verify(hc, ACCESS_RECORDER_ALL)) {
config_name = NULL;
LIST_FOREACH(cfg, &dvrconfigs, config_link) {
if (cfg->dvr_config_name && hc->hc_username &&
strcmp(cfg->dvr_config_name, hc->hc_username) == 0) {
config_name = cfg->dvr_config_name;
break;
}
}
if (config_name == NULL && hc->hc_username)
tvhlog(LOG_INFO,"dvr","User '%s' has no dvr config with identical name, using default...", hc->hc_username);
}
if (!strcmp(op, "recordEvent"))
dvr_entry_create_by_event(config_name,
e, 0, 0,
hc->hc_representative, NULL, DVR_PRIO_NORMAL);
else
dvr_autorec_add_series_link(config_name, e, hc->hc_representative, "Created from EPG query");
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "cancelEntry")) {
s = http_arg_get(&hc->hc_req_args, "entryId");
if((de = dvr_entry_find_by_id(atoi(s))) == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
dvr_entry_cancel(de);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "deleteEntry")) {
s = http_arg_get(&hc->hc_req_args, "entryId");
if((de = dvr_entry_find_by_id(atoi(s))) == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
dvr_entry_delete(de);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "createEntry")) {
const char *config_name = http_arg_get(&hc->hc_req_args, "config_name");
const char *title = http_arg_get(&hc->hc_req_args, "title");
const char *datestr = http_arg_get(&hc->hc_req_args, "date");
const char *startstr = http_arg_get(&hc->hc_req_args, "starttime");
const char *stopstr = http_arg_get(&hc->hc_req_args, "stoptime");
const char *channel = http_arg_get(&hc->hc_req_args, "channelid");
const char *pri = http_arg_get(&hc->hc_req_args, "pri");
channel_t *ch = channel ? channel_find(channel) : NULL;
if(ch == NULL || title == NULL ||
datestr == NULL || strlen(datestr) != 10 ||
startstr == NULL || strlen(startstr) != 5 ||
stopstr == NULL || strlen(stopstr) != 5) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
struct tm t = {0};
t.tm_year = atoi(datestr + 6) - 1900;
t.tm_mon = atoi(datestr) - 1;
t.tm_mday = atoi(datestr + 3);
t.tm_isdst = -1;
t.tm_hour = atoi(startstr);
t.tm_min = atoi(startstr + 3);
time_t start = mktime(&t);
t.tm_hour = atoi(stopstr);
t.tm_min = atoi(stopstr + 3);
time_t stop = mktime(&t);
if(stop < start)
stop += 86400;
if (http_access_verify(hc, ACCESS_RECORDER_ALL)) {
config_name = NULL;
LIST_FOREACH(cfg, &dvrconfigs, config_link) {
if (cfg->dvr_config_name && hc->hc_username &&
strcmp(cfg->dvr_config_name, hc->hc_username) == 0) {
config_name = cfg->dvr_config_name;
break;
}
}
if (config_name == NULL && hc->hc_username)
tvhlog(LOG_INFO,"dvr","User '%s' has no dvr config with identical name, using default...", hc->hc_username);
}
dvr_entry_create(config_name,
ch, start, stop, 0, 0, title, NULL, NULL,
0, hc->hc_representative,
NULL, dvr_pri2val(pri));
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "createAutoRec")) {
int min_duration;
int max_duration;
epg_genre_t genre, *eg = NULL;
if ((s = http_arg_get(&hc->hc_req_args, "contenttype"))) {
genre.code = atoi(s);
eg = &genre;
}
if((s = http_arg_get(&hc->hc_req_args, "minduration")) != NULL)
min_duration = atoi(s);
else
min_duration = 0;
if((s = http_arg_get(&hc->hc_req_args, "maxduration")) != NULL)
max_duration = atoi(s);
else
max_duration = INT_MAX;
dvr_autorec_add(http_arg_get(&hc->hc_req_args, "config_name"),
http_arg_get(&hc->hc_req_args, "title"),
http_arg_get(&hc->hc_req_args, "channel"),
http_arg_get(&hc->hc_req_args, "tag"),
eg, min_duration,max_duration,
hc->hc_representative, "Created from EPG query");
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "loadSettings")) {
s = http_arg_get(&hc->hc_req_args, "config_name");
if (s == NULL)
s = "";
cfg = dvr_config_find_by_name_default(s);
r = htsmsg_create_map();
htsmsg_add_str(r, "storage", cfg->dvr_storage);
htsmsg_add_str(r, "charset", cfg->dvr_charset ? cfg->dvr_charset : "UTF-8");
htsmsg_add_str(r, "container", muxer_container_type2txt(cfg->dvr_mc));
/* Convert integer permissions to an octal-format 0xxx string and store it in the config file */
snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_file_permissions);
htsmsg_add_str(r, "filePermissions", buffer);
snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_directory_permissions);
htsmsg_add_str(r, "dirPermissions", buffer);
htsmsg_add_u32(r, "cache", cfg->dvr_muxcnf.m_cache);
htsmsg_add_u32(r, "rewritePAT",
!!(cfg->dvr_muxcnf.m_flags & MC_REWRITE_PAT));
htsmsg_add_u32(r, "rewritePMT",
!!(cfg->dvr_muxcnf.m_flags & MC_REWRITE_PMT));
if(cfg->dvr_postproc != NULL)
htsmsg_add_str(r, "postproc", cfg->dvr_postproc);
htsmsg_add_u32(r, "retention", cfg->dvr_retention_days);
htsmsg_add_u32(r, "preExtraTime", cfg->dvr_extra_time_pre);
htsmsg_add_u32(r, "postExtraTime", cfg->dvr_extra_time_post);
htsmsg_add_u32(r, "dayDirs", !!(cfg->dvr_flags & DVR_DIR_PER_DAY));
htsmsg_add_u32(r, "channelDirs", !!(cfg->dvr_flags & DVR_DIR_PER_CHANNEL));
htsmsg_add_u32(r, "channelInTitle", !!(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE));
htsmsg_add_u32(r, "dateInTitle", !!(cfg->dvr_flags & DVR_DATE_IN_TITLE));
htsmsg_add_u32(r, "timeInTitle", !!(cfg->dvr_flags & DVR_TIME_IN_TITLE));
htsmsg_add_u32(r, "whitespaceInTitle", !!(cfg->dvr_flags & DVR_WHITESPACE_IN_TITLE));
htsmsg_add_u32(r, "titleDirs", !!(cfg->dvr_flags & DVR_DIR_PER_TITLE));
htsmsg_add_u32(r, "episodeInTitle", !!(cfg->dvr_flags & DVR_EPISODE_IN_TITLE));
htsmsg_add_u32(r, "cleanTitle", !!(cfg->dvr_flags & DVR_CLEAN_TITLE));
htsmsg_add_u32(r, "tagFiles", !!(cfg->dvr_flags & DVR_TAG_FILES));
htsmsg_add_u32(r, "commSkip", !!(cfg->dvr_flags & DVR_SKIP_COMMERCIALS));
htsmsg_add_u32(r, "subtitleInTitle", !!(cfg->dvr_flags & DVR_SUBTITLE_IN_TITLE));
htsmsg_add_u32(r, "episodeBeforeDate", !!(cfg->dvr_flags & DVR_EPISODE_BEFORE_DATE));
htsmsg_add_u32(r, "episodeDuplicateDetection", !!(cfg->dvr_flags & DVR_EPISODE_DUPLICATE_DETECTION));
out = json_single_record(r, "dvrSettings");
} else if(!strcmp(op, "saveSettings")) {
s = http_arg_get(&hc->hc_req_args, "config_name");
cfg = dvr_config_find_by_name(s);
if (cfg == NULL)
cfg = dvr_config_create(s);
tvhlog(LOG_INFO,"dvr","Saving configuration '%s'", cfg->dvr_config_name);
if((s = http_arg_get(&hc->hc_req_args, "storage")) != NULL)
dvr_storage_set(cfg,s);
if((s = http_arg_get(&hc->hc_req_args, "charset")) != NULL)
dvr_charset_set(cfg,s);
if((s = http_arg_get(&hc->hc_req_args, "container")) != NULL)
dvr_container_set(cfg,s);
/*
* Convert 0xxx format permission strings to integer for internal use
* Note no checking that strtol won't overflow int - this should never happen with three-digit numbers
*/
if((s = http_arg_get(&hc->hc_req_args, "filePermissions")) != NULL)
dvr_file_permissions_set(cfg,(int)strtol(s,NULL,0));
if((s = http_arg_get(&hc->hc_req_args, "dirPermissions")) != NULL)
dvr_directory_permissions_set(cfg,(int)strtol(s,NULL,0));
if((s = http_arg_get(&hc->hc_req_args, "cache")) != NULL)
dvr_mux_cache_set(cfg,atoi(s));
if((s = http_arg_get(&hc->hc_req_args, "postproc")) != NULL)
dvr_postproc_set(cfg,s);
if((s = http_arg_get(&hc->hc_req_args, "retention")) != NULL)
dvr_retention_set(cfg,atoi(s));
if((s = http_arg_get(&hc->hc_req_args, "preExtraTime")) != NULL)
dvr_extra_time_pre_set(cfg,atoi(s));
if((s = http_arg_get(&hc->hc_req_args, "postExtraTime")) != NULL)
dvr_extra_time_post_set(cfg,atoi(s));
if(http_arg_get(&hc->hc_req_args, "dayDirs") != NULL)
flags |= DVR_DIR_PER_DAY;
if(http_arg_get(&hc->hc_req_args, "channelDirs") != NULL)
flags |= DVR_DIR_PER_CHANNEL;
if(http_arg_get(&hc->hc_req_args, "channelInTitle") != NULL)
flags |= DVR_CHANNEL_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "cleanTitle") != NULL)
flags |= DVR_CLEAN_TITLE;
if(http_arg_get(&hc->hc_req_args, "dateInTitle") != NULL)
flags |= DVR_DATE_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "timeInTitle") != NULL)
flags |= DVR_TIME_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "whitespaceInTitle") != NULL)
flags |= DVR_WHITESPACE_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "titleDirs") != NULL)
flags |= DVR_DIR_PER_TITLE;
if(http_arg_get(&hc->hc_req_args, "episodeInTitle") != NULL)
flags |= DVR_EPISODE_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "tagFiles") != NULL)
flags |= DVR_TAG_FILES;
if(http_arg_get(&hc->hc_req_args, "commSkip") != NULL)
flags |= DVR_SKIP_COMMERCIALS;
if(http_arg_get(&hc->hc_req_args, "subtitleInTitle") != NULL)
flags |= DVR_SUBTITLE_IN_TITLE;
if(http_arg_get(&hc->hc_req_args, "episodeBeforeDate") != NULL)
flags |= DVR_EPISODE_BEFORE_DATE;
if(http_arg_get(&hc->hc_req_args, "episodeDuplicateDetection") != NULL)
flags |= DVR_EPISODE_DUPLICATE_DETECTION;
dvr_flags_set(cfg,flags);
/* Muxer flags */
flags = 0;
if(http_arg_get(&hc->hc_req_args, "rewritePAT") != NULL)
flags |= MC_REWRITE_PAT;
if(http_arg_get(&hc->hc_req_args, "rewritePMT") != NULL)
flags |= MC_REWRITE_PMT;
dvr_mux_flags_set(cfg, flags);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "deleteSettings")) {
s = http_arg_get(&hc->hc_req_args, "config_name");
dvr_config_delete(s);
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 int
extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque,
dvr_entry_filter filter, dvr_entry_comparator cmp)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *array, *m;
dvr_query_result_t dqr;
dvr_entry_t *de;
int start = 0, end, limit, i;
const char *s;
int64_t fsize = 0;
char buf[100];
if((s = http_arg_get(&hc->hc_req_args, "start")) != NULL)
start = atoi(s);
if((s = http_arg_get(&hc->hc_req_args, "limit")) != NULL)
limit = atoi(s);
else
limit = 20; /* XXX */
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_RECORDER)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
out = htsmsg_create_map();
array = htsmsg_create_list();
dvr_query_filter(&dqr, filter);
dvr_query_sort_cmp(&dqr, cmp);
htsmsg_add_u32(out, "totalCount", dqr.dqr_entries);
start = MIN(start, dqr.dqr_entries);
end = MIN(start + limit, dqr.dqr_entries);
for(i = start; i < end; i++) {
de = dqr.dqr_array[i];
m = htsmsg_create_map();
htsmsg_add_str(m, "channel", DVR_CH_NAME(de));
if(de->de_channel != NULL) {
htsmsg_add_str(m, "channelid", channel_get_uuid(de->de_channel));
if (de->de_channel->ch_icon)
htsmsg_add_imageurl(m, "chicon", "imagecache/%d",
de->de_channel->ch_icon);
}
htsmsg_add_str(m, "config_name", de->de_config_name);
if(de->de_title != NULL)
htsmsg_add_str(m, "title", lang_str_get(de->de_title, NULL));
if(de->de_desc != NULL)
htsmsg_add_str(m, "description", lang_str_get(de->de_desc, NULL));
if (de->de_bcast && de->de_bcast->episode)
if (epg_episode_number_format(de->de_bcast->episode, buf, 100, NULL, "Season %d", ".", "Episode %d", "/%d"))
htsmsg_add_str(m, "episode", buf);
htsmsg_add_u32(m, "id", de->de_id);
htsmsg_add_u32(m, "start", de->de_start);
htsmsg_add_u32(m, "end", de->de_stop);
htsmsg_add_u32(m, "duration", de->de_stop - de->de_start);
htsmsg_add_str(m, "creator", de->de_creator);
htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri));
htsmsg_add_str(m, "status", dvr_entry_status(de));
htsmsg_add_str(m, "schedstate", dvr_entry_schedstatus(de));
if(de->de_sched_state == DVR_COMPLETED) {
fsize = dvr_get_filesize(de);
if (fsize > 0) {
char url[100];
htsmsg_add_s64(m, "filesize", fsize);
snprintf(url, sizeof(url), "dvrfile/%d", de->de_id);
htsmsg_add_str(m, "url", url);
}
}
htsmsg_add_msg(array, NULL, m);
}
dvr_query_free(&dqr);
pthread_mutex_unlock(&global_lock);
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;
}
static int is_dvr_entry_finished(dvr_entry_t *entry)
{
dvr_entry_sched_state_t state = entry->de_sched_state;
return state == DVR_COMPLETED && !entry->de_last_error && dvr_get_filesize(entry) != -1;
}
static int is_dvr_entry_upcoming(dvr_entry_t *entry)
{
dvr_entry_sched_state_t state = entry->de_sched_state;
return state == DVR_RECORDING || state == DVR_SCHEDULED;
}
static int is_dvr_entry_failed(dvr_entry_t *entry)
{
if (is_dvr_entry_finished(entry))
return 0;
if (is_dvr_entry_upcoming(entry))
return 0;
return 1;
}
static int
extjs_dvrlist_finished(http_connection_t *hc, const char *remain, void *opaque)
{
return extjs_dvrlist(hc, remain, opaque, is_dvr_entry_finished, dvr_sort_start_descending);
}
static int
extjs_dvrlist_upcoming(http_connection_t *hc, const char *remain, void *opaque)
{
return extjs_dvrlist(hc, remain, opaque, is_dvr_entry_upcoming, dvr_sort_start_ascending);
}
static int
extjs_dvrlist_failed(http_connection_t *hc, const char *remain, void *opaque)
{
return extjs_dvrlist(hc, remain, opaque, is_dvr_entry_failed, dvr_sort_start_descending);
}
/**
*
*/
void
extjs_service_delete(htsmsg_t *in)
{
htsmsg_field_t *f;
service_t *t;
const char *id;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((id = htsmsg_field_get_string(f)) != NULL &&
(t = service_find_by_identifier(id)) != NULL)
service_destroy(t, 1);
}
}
/**
*
*/
@ -1700,17 +1062,9 @@ extjs_start(void)
http_path_add("/capabilities", NULL, extjs_capabilities, ACCESS_WEB_INTERFACE);
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
http_path_add("/epggrab", NULL, extjs_epggrab, ACCESS_WEB_INTERFACE);
http_path_add("/confignames", NULL, extjs_confignames, ACCESS_WEB_INTERFACE);
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);
http_path_add("/epgrelated", NULL, extjs_epgrelated, ACCESS_WEB_INTERFACE);
http_path_add("/epgobject", NULL, extjs_epgobject, ACCESS_WEB_INTERFACE);
http_path_add("/dvr", NULL, extjs_dvr, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist_upcoming", NULL, extjs_dvrlist_upcoming, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist_finished", NULL, extjs_dvrlist_finished, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist_failed", NULL, extjs_dvrlist_failed, ACCESS_WEB_INTERFACE);
http_path_add("/dvr_containers", NULL, extjs_dvr_containers, ACCESS_WEB_INTERFACE);
http_path_add("/dvr_caches", NULL, extjs_dvr_caches, ACCESS_WEB_INTERFACE);
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE);
http_path_add("/languages", NULL, extjs_languages, ACCESS_WEB_INTERFACE);
#if ENABLE_TIMESHIFT

View file

@ -171,7 +171,7 @@ page_simple(http_connection_t *hc,
rstatus = val2str(de->de_sched_state, recstatustxt);
htsbuf_qprintf(hq, "<a href=\"/pvrinfo/%d\">", de->de_id);
htsbuf_qprintf(hq, "<a href=\"/pvrinfo/%s\">", idnode_uuid_as_str(&de->de_id));
htsbuf_qprintf(hq,
"%02d:%02d-%02d:%02d&nbsp; %s",
@ -216,7 +216,7 @@ page_einfo(http_connection_t *hc, const char *remain, void *opaque)
de = dvr_entry_find_by_event(e);
if((http_arg_get(&hc->hc_req_args, "rec")) != NULL) {
de = dvr_entry_create_by_event("", e, 0, 0, hc->hc_username ?: "anonymous", NULL,
de = dvr_entry_create_by_event(NULL, e, 0, 0, hc->hc_username ?: "anonymous", NULL,
DVR_PRIO_NORMAL);
} else if(de != NULL && (http_arg_get(&hc->hc_req_args, "cancel")) != NULL) {
de = dvr_entry_cancel(de);
@ -328,8 +328,8 @@ page_pvrinfo(http_connection_t *hc, const char *remain, void *opaque)
if((rstatus = val2str(de->de_sched_state, recstatustxt)) != NULL)
htsbuf_qprintf(hq, "Recording status: %s<br>", rstatus);
htsbuf_qprintf(hq, "<form method=\"post\" action=\"/pvrinfo/%d\">",
de->de_id);
htsbuf_qprintf(hq, "<form method=\"post\" action=\"/pvrinfo/%s\">",
idnode_uuid_as_str(&de->de_id));
switch(de->de_sched_state) {
case DVR_SCHEDULED:

View file

@ -54,18 +54,27 @@ dumpchannels(htsbuf_queue_t *hq)
{
channel_t *ch;
outputtitle(hq, 0, "Channels");
int64_t chnum;
char chbuf[32];
CHANNEL_FOREACH(ch) {
htsbuf_qprintf(hq, "%s (%d)\n", channel_get_name(ch), channel_get_id(ch));
chnum = channel_get_number(ch);
if (channel_get_minor(chnum))
snprintf(chbuf, sizeof(chbuf), "%u.%u",
channel_get_major(chnum),
channel_get_minor(chnum));
else
snprintf(chbuf, sizeof(chbuf), "%u", channel_get_major(chnum));
htsbuf_qprintf(hq,
" refcount = %d\n"
" zombie = %d\n"
" number = %d\n"
" number = %s\n"
" icon = %s\n\n",
ch->ch_refcount,
ch->ch_zombie,
channel_get_number(ch),
chbuf,
ch->ch_icon ?: "<none set>");
}
}

View file

@ -2,32 +2,47 @@
* Access Control
*/
tvheadend.acleditor = function(panel)
tvheadend.acleditor = function(panel, index)
{
panel = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Access Control',
iconCls: 'group',
items: []
});
var list = 'enabled,username,password,prefix,streaming,adv_streaming,' +
'dvr,dvr_config,webui,admin,channel_min,channel_max,channel_tag,' +
'comment';
tvheadend.idnode_grid(panel, {
url: 'api/access/entry',
comet: 'acl_entries',
titleS: 'Access Entry',
titleP: 'Access Entries',
tabIndex: 0,
iconCls: 'group',
columns: {
username: { width: 250 },
password: { width: 250 },
prefix: { width: 350 },
streaming: { width: 100 },
adv_streaming: { width: 100 },
dvr: { width: 100 },
webui: { width: 100 },
admin: { width: 100 },
channel_min: { width: 100 },
channel_max: { width: 100 },
},
tabIndex: index,
edit: {
params: {
list: list,
},
},
add: {
url: 'api/access/entry',
params: {
list: list,
},
create: { }
},
del: true,
move: true,
list: list,
help: function() {
new tvheadend.help('Access Control Entries', 'config_access.html');
},
});
return panel;
};

View file

@ -1,4 +1,5 @@
tvheadend.capmteditor = function() {
tvheadend.capmteditor = function(panel, index) {
var fm = Ext.form;
function setMetaAttr(meta, record) {
@ -104,6 +105,8 @@ tvheadend.capmteditor = function() {
}
});
return new tvheadend.tableEditor('Capmt Connections', 'capmt', cm, rec,
var p = new tvheadend.tableEditor('Capmt Connections', 'capmt', cm, rec,
[], store, 'config_capmt.html', 'key');
tvheadend.paneladd(panel, p, index);
};

View file

@ -6,16 +6,9 @@ insertChannelTagsClearOption = function( scope, records, options ){
scope.insert(0,new placeholder({key: '-1', val: '(Clear filter)'}));
};
tvheadend.channelTags = new Ext.data.JsonStore({
tvheadend.channelTags = tvheadend.idnode_get_enum({
url: 'api/channeltag/list',
root: 'entries',
fields: ['key', 'val'],
id: 'key',
autoLoad: true,
sortInfo: {
field: 'val',
direction: 'ASC',
},
event: 'channeltag',
listeners: {
'load': insertChannelTagsClearOption
}
@ -60,11 +53,7 @@ tvheadend.comet.on('channels', function(m) {
tvheadend.channel_tab = function(panel, index)
{
function assign_low_number() {
var tab = panel.getActiveTab();
var sm = tab.getSelectionModel();
var store = tab.getStore();
function assign_low_number(ctx, e, store, sm) {
if (sm.getCount() !== 1)
return;
@ -107,20 +96,14 @@ tvheadend.channel_tab = function(panel, index)
sm.selectNext();
}
function move_number_up() {
var tab = panel.getActiveTab();
var sm = tab.getSelectionModel();
function move_number_up(ctx, e, store, sm) {
Ext.each(sm.getSelections(), function(channel) {
var number = channel.data.number;
channel.set('number', number + 1);
});
}
function move_number_down() {
var tab = panel.getActiveTab();
var sm = tab.getSelectionModel();
function move_number_down(ctx, e, store, sm) {
Ext.each(sm.getSelections(), function(channel) {
var number = channel.data.number;
@ -130,11 +113,7 @@ tvheadend.channel_tab = function(panel, index)
});
}
function swap_numbers() {
var tab = panel.getActiveTab();
var sm = tab.getSelectionModel();
var store = tab.getStore(); //store is unused
function swap_numbers(ctx, e, store, sm) {
if (sm.getCount() !== 2)
return;
@ -145,45 +124,70 @@ tvheadend.channel_tab = function(panel, index)
sel[1].set('number', tmp);
}
var mapButton = new Ext.Toolbar.Button({
tooltip: 'Map services to channels',
iconCls: 'clone',
text: 'Map Services',
handler: tvheadend.service_mapper,
disabled: false
});
var mapButton = {
name: 'map',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Map services to channels',
iconCls: 'clone',
text: 'Map Services',
disabled: false
});
},
callback: tvheadend.service_mapper
};
var lowNoButton = new Ext.Toolbar.Button({
tooltip: 'Assign lowest free channel number',
iconCls: 'bullet_add',
text: 'Assign Number',
handler: assign_low_number,
disabled: false
});
var lowNoButton = {
name: 'lowno',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Assign lowest free channel number',
iconCls: 'bullet_add',
text: 'Assign Number',
disabled: false
});
},
callback: assign_low_number
};
var noUpButton = new Ext.Toolbar.Button({
tooltip: 'Move channel one number up',
iconCls: 'arrow_up',
text: 'Number Up',
handler: move_number_up,
disabled: false
});
var noUpButton = {
name: 'noup',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Move channel one number up',
iconCls: 'arrow_up',
text: 'Number Up',
disabled: false
});
},
callback: move_number_up
};
var noDownButton = new Ext.Toolbar.Button({
tooltip: 'Move channel one number down',
iconCls: 'arrow_down',
text: 'Number Down',
handler: move_number_down,
disabled: false
});
var noDownButton = {
name: 'nodown',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Move channel one number down',
iconCls: 'arrow_down',
text: 'Number Down',
disabled: false
});
},
callback: move_number_down
};
var noSwapButton = new Ext.Toolbar.Button({
tooltip: 'Swap the two selected channels numbers',
iconCls: 'arrow_switch',
text: 'Swap Numbers',
handler: swap_numbers,
disabled: false
});
var noSwapButton = {
name: 'swap',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Swap the two selected channels numbers',
iconCls: 'arrow_switch',
text: 'Swap Numbers',
disabled: false
});
},
callback: swap_numbers
};
tvheadend.idnode_grid(panel, {
url: 'api/channel',

View file

@ -2,19 +2,7 @@
* Comet interfaces
*/
Ext.extend(tvheadend.Comet = function() {
this.addEvents({
accessUpdate: true,
tvAdapter: true,
dvbMux: true,
dvbStore: true,
dvbSatConf: true,
logmessage: true,
channeltags: true,
autorec: true,
dvrdb: true,
dvrconfig: true,
channels: true
});
this.addEvents({ });
}, Ext.util.Observable);
tvheadend.comet = new tvheadend.Comet();

View file

@ -31,7 +31,7 @@ tvheadend.comet.on('config', function(m) {
}
});
tvheadend.miscconf = function() {
tvheadend.miscconf = function(panel, index) {
/*
* Basic Config
@ -240,7 +240,7 @@ tvheadend.miscconf = function() {
if (imagecache_form)
_items.push(imagecache_form);
var panel = new Ext.Panel({
var mpanel = new Ext.Panel({
title: 'General',
iconCls: 'wrench',
border: false,
@ -251,6 +251,8 @@ tvheadend.miscconf = function() {
tbar: [saveButton, '->', helpButton]
});
tvheadend.paneladd(panel, mpanel, index);
/* ****************************************************************
* Load/Save
* ***************************************************************/
@ -297,6 +299,4 @@ tvheadend.miscconf = function() {
}
});
}
return panel;
};

View file

@ -1,4 +1,4 @@
tvheadend.cwceditor = function() {
tvheadend.cwceditor = function(panel, index) {
var fm = Ext.form;
function setMetaAttr(meta, record) {
@ -125,5 +125,5 @@ tvheadend.cwceditor = function() {
}
});
return grid;
tvheadend.paneladd(panel, grid, index);
};

File diff suppressed because it is too large Load diff

View file

@ -13,31 +13,22 @@ insertContentGroupClearOption = function( scope, records, options ){
scope.insert(0,new placeholder({name: '(Clear filter)', code: '-1'}));
};
//WIBNI: might want this store to periodically update
tvheadend.ContentGroupStore = new Ext.data.JsonStore({
root: 'entries',
fields: ['name', 'code'],
autoLoad: true,
url: 'ecglist',
tvheadend.ContentGroupStore = tvheadend.idnode_get_enum({
url: 'api/epg/content_type/list',
listeners: {
'load': insertContentGroupClearOption
load: insertContentGroupClearOption
}
});
tvheadend.contentGroupLookupName = function(code) {
ret = "";
tvheadend.ContentGroupStore.each(function(r) {
if (r.data.code === code)
ret = r.data.name;
else if (ret === "" && r.data.code === (code & 0xF0))
ret = r.data.name;
if (r.data.key === code)
ret = r.data.val;
});
return ret;
};
tvheadend.ContentGroupStore.setDefaultSort('code', 'ASC');
tvheadend.channelLookupName = function(key) {
channelString = "";
@ -103,7 +94,7 @@ tvheadend.epgDetails = function(event) {
content += '<div class="x-epg-desc">' + event.description + '</div>';
content += '<div class="x-epg-meta">' + event.starrating + '</div>';
content += '<div class="x-epg-meta">' + event.agerating + '</div>';
content += '<div class="x-epg-meta">' + tvheadend.contentGroupLookupName(event.contenttype) + '</div>';
content += '<div class="x-epg-meta">' + tvheadend.contentGroupLookupName(event.content_type) + '</div>';
if (event.ext_desc != null)
content += '<div class="x-epg-meta">' + event.ext_desc + '</div>';
@ -127,17 +118,56 @@ tvheadend.epgDetails = function(event) {
'?title=' + encodeURIComponent(title) + '">Play</a></div>';
}
var confcombo = new Ext.form.ComboBox({
store: tvheadend.configNames,
triggerAction: 'all',
mode: 'local',
valueField: 'identifier',
displayField: 'name',
name: 'config_name',
emptyText: '(default)',
value: '',
editable: false
});
var buttons = [];
if (tvheadend.accessUpdate.dvr) {
var store = new Ext.data.JsonStore({
autoload: true,
root: 'entries',
fields: ['key','val'],
id: 'key',
url: 'api/idnode/load',
baseParams: {
enum: 1,
'class': 'dvrconfig'
},
sortInfo: {
field: 'val',
direction: 'ASC'
}
});
store.load();
var confcombo = new Ext.form.ComboBox({
store: store,
triggerAction: 'all',
mode: 'local',
valueField: 'key',
displayField: 'val',
name: 'config_name',
emptyText: '(default)',
value: '',
editable: false
});
buttons.push(confcombo);
buttons.push(new Ext.Button({
handler: recordEvent,
text: "Record program"
}));
buttons.push(new Ext.Button({
handler: recordSeries,
text: event.serieslink ? "Record series" : "Autorec"
}));
} else {
buttons.push(new Ext.Button({
handler: function() { win.close(); },
text: "Close"
}));
}
var win = new Ext.Window({
title: event.title,
@ -145,33 +175,26 @@ tvheadend.epgDetails = function(event) {
width: 500,
height: 300,
constrainHeader: true,
buttons: [confcombo, new Ext.Button({
handler: recordEvent,
text: "Record program"
}), new Ext.Button({
handler: recordSeries,
text: event.serieslink ? "Record series" : "Autorec"
})],
buttons: buttons,
buttonAlign: 'center',
html: content
});
win.show();
function recordEvent() {
record('recordEvent');
record('api/dvr/entry/create_by_event');
}
function recordSeries() {
record('recordSeries');
record('api/dvr/autorec/create_by_series');
}
function record(op) {
function record(url) {
Ext.Ajax.request({
url: 'dvr',
url: url,
params: {
eventId: event.id,
op: op,
config_name: confcombo.getValue()
event_id: event.id,
config_uuid: confcombo.getValue()
},
success: function(response, options) {
win.close();
@ -232,7 +255,7 @@ tvheadend.epg = function() {
}, {
name: 'agerating'
}, {
name: 'contenttype'
name: 'content_type'
}, {
name: 'schedstate'
}, {
@ -244,7 +267,7 @@ tvheadend.epg = function() {
var now = new Date;
var start = record.get('start');
if (now.getTime() > start.getTime()) {
if (now.getTime() >= start.getTime()) {
meta.attr = 'style="font-weight:bold;"';
}
}
@ -302,7 +325,7 @@ tvheadend.epg = function() {
var now = new Date();
// Only render a progress bar for currently running programmes
if (now <= end && now >= start)
if (now >= start)
return (now - start) / 1000 / duration * 100;
else
return "";
@ -364,9 +387,9 @@ tvheadend.epg = function() {
renderer: renderInt
}, {
width: 250,
id: 'contenttype',
id: 'content_type',
header: "Content Type",
dataIndex: 'contenttype',
dataIndex: 'content_type',
renderer: function(v) {
return tvheadend.contentGroupLookupName(v);
}
@ -490,12 +513,12 @@ tvheadend.epg = function() {
};
clearContentGroupFilter = function() {
delete epgStore.baseParams.contenttype;
delete epgStore.baseParams.content_type;
epgFilterContentGroup.setValue("");
};
clearDurationFilter = function() {
delete epgStore.baseParams.minduration;
delete epgStore.baseParams.minduration;
delete epgStore.baseParams.maxduration;
epgFilterDuration.setValue("");
};
@ -532,8 +555,8 @@ tvheadend.epg = function() {
epgFilterContentGroup.on('select', function(c, r) {
if (r.data.code == -1)
clearContentGroupFilter();
else if (epgStore.baseParams.contenttype !== r.data.code)
epgStore.baseParams.contenttype = r.data.code;
else if (epgStore.baseParams.content_type !== r.data.code)
epgStore.baseParams.content_type = r.data.code;
epgStore.reload();
});
@ -566,6 +589,44 @@ tvheadend.epg = function() {
}
});
tvheadend.autorecButton = new Ext.Button({
text: 'Create AutoRec',
iconCls: 'wand',
tooltip: 'Create an automatic recording entry that will '
+ 'record all future programmes that matches '
+ 'the current query.',
handler: createAutoRec
});
var tbar = [
epgFilterTitle, '-',
epgFilterChannels, '-',
epgFilterChannelTags, '-',
epgFilterContentGroup, '-',
epgFilterDuration, '-',
{
text: 'Reset All',
handler: epgQueryClear
},
'->',
{
text: 'Watch TV',
iconCls: 'eye',
handler: function() {
new tvheadend.VideoPlayer();
}
},
'-',
tvheadend.autorecButton,
'-',
{
text: 'Help',
handler: function() {
new tvheadend.help('Electronic Program Guide', 'epg.html');
}
}
];
var panel = new Ext.ux.grid.livegrid.GridPanel({
stateful: true,
stateId: 'epggrid',
@ -577,43 +638,7 @@ tvheadend.epg = function() {
store: epgStore,
selModel: new Ext.ux.grid.livegrid.RowSelectionModel(),
view: epgView,
tbar: [
epgFilterTitle,
'-',
epgFilterChannels,
'-',
epgFilterChannelTags,
'-',
epgFilterContentGroup,
'-',
epgFilterDuration,
'-',
{
text: 'Reset All',
handler: epgQueryClear
},
'->',
{
text: 'Watch TV',
iconCls: 'eye',
handler: function() {
new tvheadend.VideoPlayer();
}
},
'-',
{
text: 'Create AutoRec',
iconCls: 'wand',
tooltip: 'Create an automatic recording entry that will '
+ 'record all future programmes that matches '
+ 'the current query.',
handler: createAutoRec
}, '-', {
text: 'Help',
handler: function() {
new tvheadend.help('Electronic Program Guide', 'epg.html');
}
}],
tbar: tbar,
bbar: new Ext.ux.grid.livegrid.Toolbar({
view: epgView,
displayInfo: true
@ -643,6 +668,9 @@ tvheadend.epg = function() {
}
function createAutoRec() {
if (!tvheadend.accessUpdate.dvr)
return;
var title = epgStore.baseParams.title ? epgStore.baseParams.title
: "<i>Don't care</i>";
@ -650,19 +678,18 @@ tvheadend.epg = function() {
: "<i>Don't care</i>";
var tag = epgStore.baseParams.tag ? tvheadend.tagLookupName(epgStore.baseParams.tag)
: "<i>Don't care</i>";
var contenttype = epgStore.baseParams.contenttype ? tvheadend.contentGroupLookupName(epgStore.baseParams.contenttype)
var content_type = epgStore.baseParams.content_type ? tvheadend.contentGroupLookupName(epgStore.baseParams.content_type)
: "<i>Don't care</i>";
var duration = epgStore.baseParams.minduration ? tvheadend.durationLookupRange(epgStore.baseParams.minduration)
: "<i>Don't care</i>";
Ext.MessageBox.confirm('Auto Recorder', 'This will create an automatic rule that '
+ 'continuously scans the EPG for programmes '
+ 'to record that match this query: ' + '<br><br>'
+ '<div class="x-smallhdr">Title:</div>' + title + '<br>'
+ '<div class="x-smallhdr">Channel:</div>' + channel + '<br>'
+ '<div class="x-smallhdr">Tag:</div>' + tag + '<br>'
+ '<div class="x-smallhdr">Genre:</div>' + contenttype + '<br>'
+ '<div class="x-smallhdr">Genre:</div>' + content_type + '<br>'
+ '<div class="x-smallhdr">Duration:</div>' + duration + '<br>'
+ '<br><br>' + 'Currently this will match (and record) '
+ epgStore.getTotalCount() + ' events. ' + 'Are you sure?',
@ -675,10 +702,19 @@ tvheadend.epg = function() {
function createAutoRec2(params) {
/* Really do it */
params.op = 'createAutoRec';
var conf = {
enabled: 1,
comment: 'Created from EPG query',
};
if (params.title) conf.title = params.title;
if (params.channel) conf.channel = params.channel;
if (params.tag) conf.tag = params.tag;
if (params.content_type) conf.content_type = params.content_type;
if (params.minduration) conf.minduration = params.minduration;
if (params.maxduration) conf.maxduration = params.maxduration;
Ext.Ajax.request({
url: 'dvr',
params: params
url: 'api/dvr/autorec/create',
params: { conf: Ext.encode(conf) }
});
}

View file

@ -8,7 +8,7 @@ tvheadend.epggrabChannels = new Ext.data.JsonStore({
'mod-name']
});
tvheadend.epggrab = function() {
tvheadend.epggrab = function(panel, index) {
/* ****************************************************************
* Data
@ -394,5 +394,5 @@ tvheadend.epggrab = function() {
});
}
return confpanel;
tvheadend.paneladd(panel, confpanel, index);
};

View file

@ -6,7 +6,6 @@ tvheadend.esfilter_tab = function(panel)
{
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/video',
comet: 'esfilter_video',
titleS: 'Video Stream Filter',
titleP: 'Video Stream Filters',
tabIndex: 0,
@ -23,7 +22,6 @@ tvheadend.esfilter_tab = function(panel)
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/audio',
comet: 'esfilter_audio',
titleS: 'Audio Stream Filter',
titleP: 'Audio Stream Filters',
tabIndex: 1,
@ -40,7 +38,6 @@ tvheadend.esfilter_tab = function(panel)
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/teletext',
comet: 'esfilter_teletext',
titleS: 'Teletext Stream Filter',
titleP: 'Teletext Stream Filters',
tabIndex: 2,
@ -57,7 +54,6 @@ tvheadend.esfilter_tab = function(panel)
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/subtit',
comet: 'esfilter_subtit',
titleS: 'Subtitle Stream Filter',
titleP: 'Subtitle Stream Filters',
tabIndex: 3,
@ -74,7 +70,6 @@ tvheadend.esfilter_tab = function(panel)
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/ca',
comet: 'esfilter_ca',
titleS: 'CA Stream Filter',
titleP: 'CA Stream Filters',
tabIndex: 4,
@ -91,7 +86,6 @@ tvheadend.esfilter_tab = function(panel)
tvheadend.idnode_grid(panel, {
url: 'api/esfilter/other',
comet: 'esfilter_other',
titleS: 'Other Stream Filter',
titleP: 'Other Stream Filters',
tabIndex: 5,

View file

@ -24,6 +24,24 @@
text-shadow: 0 -2px rgba(0, 0, 0, 0.2);
}
.x-tab-strip li.x-tab-login {
margin-left: 16px;
}
.x-tab-strip span.x-tab-strip-login {
vertical-align: middle;
white-space: nowrap;
padding:4px 0px;
}
.x-tab-strip span.x-tab-strip-login-cmd {
vertical-align: middle;
cursor:pointer;
padding:4px 0;
float: right;
}
.x-tree-col {
float: left;
overflow: hidden;
@ -148,6 +166,10 @@
background-image: url(../icons/delete.png) !important;
}
.cancel {
background-image: url(../icons/cancel.png) !important;
}
.moveup {
background-image: url(../icons/arrow_up.png) !important;
}
@ -294,7 +316,6 @@
.arrow_switch {
background-image: url(../icons/arrow_switch.png) !important;
}
.stream_config {

View file

@ -7,8 +7,6 @@
*/
/*
* Ext JS Library 2.2
* Copyright(c) 2006-2008, Ext JS, LLC.
@ -1237,3 +1235,782 @@ Ext.ux.form.LovCombo = Ext.extend(Ext.form.ComboBox, {
// register xtype
Ext.reg('lovcombo', Ext.ux.form.LovCombo);
/**
* @class Ext.ux.form.TwinDateTimeField
* @extends Ext.form.Field
*
* DateTime field, combination of DateField and TimeField
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, Ing. Jozef Sakáloš
* @version 2.0
* @revision $Id: Ext.ux.form.TwinDateTimeField.js 813 2010-01-29 23:32:36Z jozo $
*
* @license Ext.ux.form.TwinDateTimeField 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.
*
* <p>
* License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a>
* </p>
*/
Ext.ns('Ext.ux.form');
// register xtype
//Ext.reg('twindatetime', Ext.ux.form.TwinDateTimeField);
//Ext.namespace('Ext.ux.form');
Ext.ux.form.TwinDateField = Ext.extend(Ext.form.DateField, {
getTrigger : Ext.form.TwinTriggerField.prototype.getTrigger,
initTrigger : Ext.form.TwinTriggerField.prototype.initTrigger,
initComponent : Ext.form.TwinTriggerField.prototype.initComponent,
trigger2Class : 'x-form-date-trigger',
trigger1Class : 'x-form-clear-trigger',
hideTrigger1 : true,
submitOnSelect : true,
submitOnClear : true,
allowClear : true,
defaultValue : null,
onSelect : Ext.form.DateField.prototype.onSelect.createSequence(function(v) {
if (this.value && this.ownerCt && this.ownerCt.buttons && this.submitOnSelect) {
this.ownerCt.buttons[0].handler.call(this.ownerCt);
}
}),
onRender : Ext.form.DateField.prototype.onRender.createSequence(function(v) {
this.getTrigger(0).hide();
}),
setValue : Ext.form.DateField.prototype.setValue.createSequence(function(v) {
if (v !== null && v != '') {
if (this.allowClear)
this.getTrigger(0).show();
} else {
this.getTrigger(0).hide();
}
}),
reset : Ext.form.DateField.prototype.reset.createSequence(function() {
this.originalValue = this.defaultValue;
this.setValue(this.defaultValue);
if (this.allowClear)
this.getTrigger(0).hide();
}),
onTrigger2Click : function() {
if (!this.readOnly)
this.onTriggerClick();
},
onTrigger1Click : function() {
if (!this.disabled && !this.readOnly) {
this.clearValue();
this.getTrigger(0).hide();
if (this.ownerCt && this.ownerCt.buttons && this.submitOnClear) {
this.ownerCt.buttons[0].handler.call(this.ownerCt);
}
this.fireEvent('clear', this);
this.onFocus();
}
},
/**
* Clears any text/value currently set in the field
*/
clearValue : function() {
if (this.hiddenField) {
this.hiddenField.value = '';
}
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
this.value = '';
}
});
//Ext.ComponentMgr.registerType('twindatefield', Ext.ux.form.TwinDateField);
/**
* Creates new DateTime
*
* @constructor
* @param {Object}
* config A config object
*/
Ext.ux.form.TwinDateTimeField = Ext.extend(Ext.form.Field, {
/**
* @cfg {Function} dateValidator A custom validation function to be called
* during date field validation (defaults to null)
*/
dateValidator : null,
/**
* @cfg {String/Object} defaultAutoCreate DomHelper element spec Let
* superclass to create hidden field instead of textbox. Hidden will be
* submittend to server
*/
defaultAutoCreate : {
tag : 'input',
type : 'hidden'
},
/**
* @cfg {String} dtSeparator Date - Time separator. Used to split date and
* time (defaults to ' ' (space))
*/
dtSeparator : ' ',
/**
* @cfg {String} hiddenFormat Format of datetime used to store value in hidden
* field and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql
* format)
*/
hiddenFormat : 'Y-m-d H:i:s',
/**
* @cfg {Boolean} otherToNow Set other field to now() if not explicly filled
* in (defaults to true)
*/
otherToNow : true,
/**
* @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty
* value. If it is true then setValue() sets value of field to current
* date and time (defaults to false)
*/
/**
* @cfg {String} timePosition Where the time field should be rendered. 'right'
* is suitable for forms and 'below' is suitable if the field is used as
* the grid editor (defaults to 'right')
*/
timePosition : 'right', // valid values:'below', 'right'
/**
* @cfg {Function} timeValidator A custom validation function to be called
* during time field validation (defaults to null)
*/
timeValidator : null,
/**
* @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
*/
timeWidth : 100,
/**
* @cfg {String} dateFormat Format of DateField. Can be localized. (defaults
* to 'm/y/d')
*/
dateFormat : 'm/d/Y',
/**
* @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults
* to 'g:i A')
*/
timeFormat : 'g:i A',
/**
* @cfg {Object} dateConfig Config for DateField constructor.
*/
/**
* @cfg {Object} timeConfig Config for TimeField constructor.
*/
/**
* @private creates DateField and TimeField and installs the necessary event
* handlers
*/
initComponent : function() {
// call parent initComponent
Ext.ux.form.TwinDateTimeField.superclass.initComponent.call(this);
if (this.value) this.value = this.value * 1000;
// create DateField
var dateConfig = Ext.apply({}, {
id : this.id + '-date',
format : this.dateFormat || Ext.ux.form.TwinDateField.prototype.format,
width : this.timeWidth,
selectOnFocus : this.selectOnFocus,
validator : this.dateValidator,
listeners : {
blur : {
scope : this,
fn : this.onBlur
},
focus : {
scope : this,
fn : this.onFocus
}
}
}, this.dateConfig);
this.df = new Ext.ux.form.TwinDateField(dateConfig);
this.df.ownerCt = this;
delete(this.dateFormat);
// create TimeField
var timeConfig = Ext.apply({}, {
id : this.id + '-time',
format : this.timeFormat || Ext.form.TimeField.prototype.format,
width : this.timeWidth,
selectOnFocus : this.selectOnFocus,
validator : this.timeValidator,
listeners : {
blur : {
scope : this,
fn : this.onBlur
},
focus : {
scope : this,
fn : this.onFocus
}
}
}, this.timeConfig);
this.tf = new Ext.form.TimeField(timeConfig);
this.tf.ownerCt = this;
delete(this.timeFormat);
// relay events
this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);
this.on('specialkey', this.onSpecialKey, this);
},
/**
* @private Renders underlying DateField and TimeField and provides a
* workaround for side error icon bug
*/
onRender : function(ct, position) {
// don't run more than once
if (this.isRendered) {
return;
}
// render underlying hidden field
Ext.ux.form.TwinDateTimeField.superclass.onRender.call(this, ct, position);
// render DateField and TimeField
// create bounding table
var t;
if ('below' === this.timePosition || 'bellow' === this.timePosition) {
t = Ext.DomHelper.append(ct, {
tag : 'table',
style : 'border-collapse:collapse',
children : [{
tag : 'tr',
children : [{
tag : 'td',
style : 'padding-bottom:1px',
cls : 'ux-datetime-date'
}]
}, {
tag : 'tr',
children : [{
tag : 'td',
cls : 'ux-datetime-time'
}]
}]
}, true);
} else {
t = Ext.DomHelper.append(ct, {
tag : 'table',
style : 'border-collapse:collapse',
children : [{
tag : 'tr',
children : [{
tag : 'td',
style : 'padding-right:4px',
cls : 'ux-datetime-date'
}, {
tag : 'td',
cls : 'ux-datetime-time'
}]
}]
}, true);
}
this.tableEl = t;
this.wrap = t.wrap({
cls : 'x-form-field-wrap'
});
// this.wrap = t.wrap();
this.wrap.on("mousedown", this.onMouseDown, this, {
delay : 10
});
// render DateField & TimeField
this.df.render(t.child('td.ux-datetime-date'));
this.tf.render(t.child('td.ux-datetime-time'));
// workaround for IE trigger misalignment bug
// see http://extjs.com/forum/showthread.php?p=341075#post341075
// if(Ext.isIE && Ext.isStrict) {
// t.select('input').applyStyles({top:0});
// }
this.df.el.swallowEvent(['keydown', 'keypress']);
this.tf.el.swallowEvent(['keydown', 'keypress']);
// create icon for side invalid errorIcon
if ('side' === this.msgTarget) {
var elp = this.el.findParent('.x-form-element', 10, true);
if (elp) {
this.errorIcon = elp.createChild({
cls : 'x-form-invalid-icon'
});
}
var o = {
errorIcon : this.errorIcon,
msgTarget : 'side',
alignErrorIcon : this.alignErrorIcon.createDelegate(this)
};
Ext.apply(this.df, o);
Ext.apply(this.tf, o);
// this.df.errorIcon = this.errorIcon;
// this.tf.errorIcon = this.errorIcon;
}
// setup name for submit
this.el.dom.name = this.hiddenName || this.name || this.id;
// prevent helper fields from being submitted
this.df.el.dom.removeAttribute("name");
this.tf.el.dom.removeAttribute("name");
// we're rendered flag
this.isRendered = true;
// update hidden field
this.updateHidden();
},
/**
* @private
*/
adjustSize : Ext.BoxComponent.prototype.adjustSize,
/**
* @private
*/
alignErrorIcon : function() {
this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
},
/**
* @private initializes internal dateValue
*/
initDateValue : function() {
this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
},
/**
* Calls clearInvalid on the DateField and TimeField
*/
clearInvalid : function() {
this.df.clearInvalid();
this.tf.clearInvalid();
},
/**
* Calls markInvalid on both DateField and TimeField
*
* @param {String}
* msg Invalid message to display
*/
markInvalid : function(msg) {
this.df.markInvalid(msg);
this.tf.markInvalid(msg);
},
/**
* @private called from Component::destroy. Destroys all elements and removes
* all listeners we've created.
*/
beforeDestroy : function() {
if (this.isRendered) {
// this.removeAllListeners();
this.wrap.removeAllListeners();
this.wrap.remove();
this.tableEl.remove();
this.df.destroy();
this.tf.destroy();
}
},
/**
* Disable this component.
*
* @return {Ext.Component} this
*/
disable : function() {
if (this.isRendered) {
this.df.disabled = this.disabled;
this.df.onDisable();
this.tf.onDisable();
}
this.disabled = true;
this.df.disabled = true;
this.tf.disabled = true;
this.fireEvent("disable", this);
return this;
},
/**
* Enable this component.
*
* @return {Ext.Component} this
*/
enable : function() {
if (this.rendered) {
this.df.onEnable();
this.tf.onEnable();
}
this.disabled = false;
this.df.disabled = false;
this.tf.disabled = false;
this.fireEvent("enable", this);
return this;
},
/**
* @private Focus date filed
*/
focus : function() {
this.df.focus();
},
/**
* @private
*/
getPositionEl : function() {
return this.wrap;
},
/**
* @private
*/
getResizeEl : function() {
return this.wrap;
},
/**
* @return {Date/String} Returns value of this field
*/
getValue : function() {
// create new instance of date
return this.dateValue ? parseInt(this.dateValue.getTime() / 1000) : '';
},
/**
* @return {Boolean} true = valid, false = invalid
* @private Calls isValid methods of underlying DateField and TimeField and
* returns the result
*/
isValid : function() {
return this.df.isValid() && this.tf.isValid();
},
/**
* Returns true if this component is visible
*
* @return {boolean}
*/
isVisible : function() {
return this.df.rendered && this.df.getActionEl().isVisible();
},
/**
* @private Handles blur event
*/
onBlur : function(f) {
// called by both DateField and TimeField blur events
// revert focus to previous field if clicked in between
if (this.wrapClick) {
f.focus();
this.wrapClick = false;
}
// update underlying value
if (f === this.df) {
this.updateDate();
} else {
this.updateTime();
}
this.updateHidden();
this.validate();
// fire events later
(function() {
if (!this.df.hasFocus && !this.tf.hasFocus) {
var v = this.getValue();
if (String(v) !== String(this.startValue)) {
this.fireEvent("change", this, v, this.startValue);
}
this.hasFocus = false;
this.fireEvent('blur', this);
}
}).defer(100, this);
},
/**
* @private Handles focus event
*/
onFocus : function() {
if (!this.hasFocus) {
this.hasFocus = true;
this.startValue = this.getValue();
this.fireEvent("focus", this);
}
},
/**
* @private Just to prevent blur event when clicked in the middle of fields
*/
onMouseDown : function(e) {
if (!this.disabled) {
this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
}
},
/**
* @private Handles Tab and Shift-Tab events
*/
onSpecialKey : function(t, e) {
var key = e.getKey();
if (key === e.TAB) {
if (t === this.df && !e.shiftKey) {
e.stopEvent();
this.tf.focus();
}
if (t === this.tf && e.shiftKey) {
e.stopEvent();
this.df.focus();
}
this.updateValue();
}
// otherwise it misbehaves in editor grid
if (key === e.ENTER) {
this.updateValue();
}
},
/**
* Resets the current field value to the originally loaded value and clears
* any validation messages. See Ext.form.BasicForm.trackResetOnLoad
*/
reset : function() {
this.df.setValue(this.originalValue);
this.tf.setValue(this.originalValue);
},
/**
* @private Sets the value of DateField
*/
setDate : function(date) {
this.df.setValue(date);
},
/**
* @private Sets the value of TimeField
*/
setTime : function(date) {
this.tf.setValue(date);
},
/**
* @private Sets correct sizes of underlying DateField and TimeField With
* workarounds for IE bugs
*/
setSize : function(w, h) {
if (!w) {
return;
}
if ('below' === this.timePosition) {
this.df.setSize(w, h);
this.tf.setSize(w, h);
if (Ext.isIE) {
this.df.el.up('td').setWidth(w);
this.tf.el.up('td').setWidth(w);
}
} else {
this.df.setSize(w - this.timeWidth - 4, h);
this.tf.setSize(this.timeWidth, h);
if (Ext.isIE) {
this.df.el.up('td').setWidth(w - this.timeWidth - 4);
this.tf.el.up('td').setWidth(this.timeWidth);
}
}
},
/**
* @param {Mixed}
* val Value to set Sets the value of this field
*/
setValue : function(val) {
if (!val && true === this.emptyToNow) {
this.setValue(new Date());
return;
} else if (!val) {
this.setDate('');
this.setTime('');
this.updateValue();
return;
}
if ('number' === typeof val) {
val = new Date(val);
} else if ('string' === typeof val && this.hiddenFormat) {
val = Date.parseDate(val, this.hiddenFormat);
}
val = val ? val : new Date(1970, 0, 1, 0, 0, 0);
var da;
if (val instanceof Date) {
this.setDate(val);
this.setTime(val);
this.dateValue = new Date(Ext.isIE ? val.getTime() : val);
} else {
da = val.split(this.dtSeparator);
this.setDate(da[0]);
if (da[1]) {
if (da[2]) {
// add am/pm part back to time
da[1] += da[2];
}
this.setTime(da[1]);
}
}
this.updateValue();
},
/**
* Hide or show this component by boolean
*
* @return {Ext.Component} this
*/
setVisible : function(visible) {
if (visible) {
this.df.show();
this.tf.show();
} else {
this.df.hide();
this.tf.hide();
}
return this;
},
show : function() {
return this.setVisible(true);
},
hide : function() {
return this.setVisible(false);
},
/**
* @private Updates the date part
*/
updateDate : function() {
var d = this.df.getValue();
if (d) {
if (!(this.dateValue instanceof Date)) {
this.initDateValue();
if (!this.tf.getValue()) {
this.setTime(this.dateValue);
}
}
this.dateValue.setMonth(0); // because of leap years
this.dateValue.setFullYear(d.getFullYear());
this.dateValue.setMonth(d.getMonth(), d.getDate());
// this.dateValue.setDate(d.getDate());
} else {
this.dateValue = '';
this.setTime('');
}
},
/**
* @private Updates the time part
*/
updateTime : function() {
var t = this.tf.getValue();
if (t && !(t instanceof Date)) {
t = Date.parseDate(t, this.tf.format);
}
if (t && !this.df.getValue()) {
this.initDateValue();
this.setDate(this.dateValue);
}
if (this.dateValue instanceof Date) {
if (t) {
this.dateValue.setHours(t.getHours());
this.dateValue.setMinutes(t.getMinutes());
this.dateValue.setSeconds(t.getSeconds());
} else {
this.dateValue.setHours(0);
this.dateValue.setMinutes(0);
this.dateValue.setSeconds(0);
}
}
},
/**
* @private Updates the underlying hidden field value
*/
updateHidden : function() {
if (this.isRendered) {
var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
this.el.dom.value = value;
}
},
/**
* @private Updates all of Date, Time and Hidden
*/
updateValue : function() {
this.updateDate();
this.updateTime();
this.updateHidden();
return;
},
/**
* @return {Boolean} true = valid, false = invalid calls validate methods of
* DateField and TimeField
*/
validate : function() {
return this.df.validate() && this.tf.validate();
},
/**
* Returns renderer suitable to render this field
*
* @param {Object}
* Column model config
*/
renderer : function(field) {
var format = field.editor.dateFormat || Ext.ux.form.TwinDateTimeField.prototype.dateFormat;
format += ' ' + (field.editor.timeFormat || Ext.ux.form.TwinDateTimeField.prototype.timeFormat);
var renderer = function(val) {
var retval = Ext.util.Format.date(val, format);
return retval;
};
return renderer;
}
});

File diff suppressed because it is too large Load diff

View file

@ -1,318 +0,0 @@
/**
* IPTV service grid
*/
tvheadend.iptv = function(adapterId) {
var servicetypeStore = new Ext.data.JsonStore({
root: 'entries',
id: 'val',
url: '/iptv/services',
baseParams: {
op: 'servicetypeList'
},
fields: ['val', 'str'],
autoLoad: false,
sortInfo: {
field: 'channelname',
direction: 'ASC'
}
});
var fm = Ext.form;
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: "servicedetails/" + record.id,
success: function(response, options) {
r = Ext.util.JSON.decode(response.responseText);
tvheadend.showTransportDetails(r);
}
});
}
}]
});
var cm = new Ext.grid.ColumnModel({
defaultSortable: true,
columns: [
{
xtype: 'checkcolumn',
header: "Enabled",
dataIndex: 'enabled',
width: 45
},
{
header: "Channel name",
dataIndex: 'channelname',
width: 150,
renderer: function(value, metadata, record, row, col, store) {
return value ? value
: '<span class="tvh-grid-unset">Unmapped</span>';
},
editor: new fm.ComboBox({
store: tvheadend.channels,
allowBlank: true,
typeAhead: true,
minChars: 2,
lazyRender: true,
triggerAction: 'all',
mode: 'local',
displayField: 'name'
})
},
{
header: "Interface",
dataIndex: 'interface',
width: 100,
renderer: function(value, metadata, record, row, col, store) {
return value ? value : '<span class="tvh-grid-unset">Unset</span>';
},
editor: new fm.TextField({
allowBlank: false
})
},
{
header: "Group",
dataIndex: 'group',
width: 100,
renderer: function(value, metadata, record, row, col, store) {
return value ? value : '<span class="tvh-grid-unset">Unset</span>';
},
editor: new fm.TextField({
allowBlank: false
})
},
{
header: "UDP Port",
dataIndex: 'port',
width: 60,
editor: new fm.NumberField({
minValue: 1,
maxValue: 65535
})
},
{
header: "Service ID",
dataIndex: 'sid',
width: 50,
hidden: true
},
{
header: 'Service Type',
width: 100,
dataIndex: 'stype',
hidden: true,
editor: new fm.ComboBox({
valueField: 'val',
displayField: 'str',
forceSelection: false,
editable: false,
mode: 'local',
triggerAction: 'all',
store: servicetypeStore
}),
renderer: function(value, metadata, record, row, col, store) {
var val = value ? servicetypeStore.getById(value) : null;
return val ? val.get('str')
: '<span class="tvh-grid-unset">Unset</span>';
}
}, {
header: "PMT PID",
dataIndex: 'pmt',
width: 50,
hidden: true
}, {
header: "PCR PID",
dataIndex: 'pcr',
width: 50,
hidden: true
}, actions]});
var rec = Ext.data.Record.create(['id', 'enabled', 'channelname',
'interface', 'group', 'port', 'sid', 'pmt', 'pcr', 'stype']);
var store = new Ext.data.JsonStore({
root: 'entries',
fields: rec,
url: "iptv/services",
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 addRecord() {
Ext.Ajax.request({
url: "iptv/services",
params: {
op: "create"
},
failure: function(response, options) {
Ext.MessageBox.alert('Server Error',
'Unable to generate new record');
},
success: function(response, options) {
var responseData = Ext.util.JSON.decode(response.responseText);
var p = new rec(responseData, responseData.id);
grid.stopEditing();
store.insert(0, p);
grid.startEditing(0, 0);
}
});
}
;
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: "iptv/services",
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: "iptv/services",
params: {
op: "update",
entries: Ext.encode(out)
},
success: function(response, options) {
store.commitChanges();
},
failure: function(response, options) {
Ext.MessageBox.alert('Message', response.statusText);
}
});
}
var delButton = new Ext.Toolbar.Button({
tooltip: 'Delete one or more selected rows',
iconCls: 'remove',
text: 'Delete selected services',
handler: delSelected,
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
});
var selModel = new Ext.grid.RowSelectionModel({
singleSelect: false
});
var grid = new Ext.grid.EditorGridPanel({
stripeRows: true,
title: 'IPTV',
iconCls: 'iptv',
plugins: [actions],
store: store,
clicksToEdit: 2,
cm: cm,
viewConfig: {
forceFit: true
},
selModel: selModel,
tbar: [
{
tooltip: 'Create a new entry on the server. '
+ 'The new entry is initially disabled so it must be enabled '
+ 'before it start taking effect.',
iconCls: 'add',
text: 'Add service',
handler: addRecord
}, '-', delButton, '-', saveBtn, rejectBtn, '->',
{
text: 'Help',
handler: function() {
new tvheadend.help('IPTV', 'config_iptv.html');
}
}]
});
store.on('update', function(s, r, o) {
d = s.getModifiedRecords().length === 0;
saveBtn.setDisabled(d);
rejectBtn.setDisabled(d);
});
selModel.on('selectionchange', function(self) {
delButton.setDisabled(self.getCount() === 0);
});
return grid;
};

View file

@ -25,14 +25,13 @@ tvheadend.comet.on('mpegts_network', function() {
tvheadend.network_list.reload();
});
tvheadend.networks = function(panel)
tvheadend.networks = function(panel, index)
{
tvheadend.idnode_grid(panel, {
url: 'api/mpegts/network',
comet: 'mpegts_network',
titleS: 'Network',
titleP: 'Networks',
tabIndex: 1,
tabIndex: index,
help: function() {
new tvheadend.help('Networks', 'config_networks.html');
},
@ -57,14 +56,13 @@ tvheadend.networks = function(panel)
});
};
tvheadend.muxes = function(panel)
tvheadend.muxes = function(panel, index)
{
tvheadend.idnode_grid(panel, {
url: 'api/mpegts/mux',
comet: 'mpegts_mux',
titleS: 'Mux',
titleP: 'Muxes',
tabIndex: 2,
tabIndex: index,
hidemode: true,
help: function() {
new tvheadend.help('Muxes', 'config_muxes.html');
@ -195,53 +193,71 @@ tvheadend.show_service_streams = function(data) {
win.show();
};
tvheadend.services = function(panel)
tvheadend.services = function(panel, index)
{
var mapButton = new Ext.Toolbar.Button({
tooltip: 'Map services to channels',
iconCls: 'clone',
text: 'Map All',
callback: tvheadend.service_mapper,
disabled: false
});
var selected = function(s)
{
if (s.getCount() > 0)
mapButton.setText('Map Selected');
else
mapButton.setText('Map All');
};
var actions = new Ext.ux.grid.RowActions({
header: 'Details',
width: 10,
actions: [{
iconCls: 'info',
qtip: 'Detailed stream info',
cb: function(grid, rec, act, row, col) {
Ext.Ajax.request({
url: 'api/service/streams',
params: {
uuid: rec.id
},
success: function(r, o) {
var d = Ext.util.JSON.decode(r.responseText);
tvheadend.show_service_streams(d);
}
});
}
}]
});
function builder(conf) {
var mapButton = {
name: 'map',
builder: function() {
return new Ext.Toolbar.Button({
tooltip: 'Map services to channels',
iconCls: 'clone',
text: 'Map All',
disabled: false
});
},
callback: tvheadend.service_mapper
};
var selected = function(s, abuttons)
{
if (s.getCount() > 0)
abuttons.map.setText('Map Selected');
else
abuttons.map.setText('Map All');
};
var actions = new Ext.ux.grid.RowActions({
header: 'Details',
width: 10,
actions: [{
iconCls: 'info',
qtip: 'Detailed stream info',
cb: function(grid, rec, act, row, col) {
Ext.Ajax.request({
url: 'api/service/streams',
params: {
uuid: rec.id
},
success: function(r, o) {
var d = Ext.util.JSON.decode(r.responseText);
tvheadend.show_service_streams(d);
}
});
}
}],
destroy: function() {
}
});
conf.tbar = [mapButton];
conf.selected = selected;
conf.lcol[1] = actions;
conf.plugins = [actions];
}
function destroyer(conf) {
delete conf.tbar;
delete conf.plugins;
conf.lcol[1] = {};
conf.selected = null;
}
tvheadend.idnode_grid(panel, {
url: 'api/mpegts/service',
comet: 'service',
titleS: 'Service',
titleP: 'Services',
tabIndex: 3,
tabIndex: index,
hidemode: true,
add: false,
del: false,
selected: selected,
tbar: [mapButton],
help: function() {
new tvheadend.help('Services', 'config_services.html');
},
@ -255,24 +271,26 @@ tvheadend.services = function(panel)
"?title=" + encodeURIComponent(title) + "'>Play</a>";
}
},
actions
{
/* placeholder for actions */
}
],
plugins: [actions],
sort: {
field: 'svcname',
direction: 'ASC'
}
},
builder: builder,
destroyer: destroyer
});
};
tvheadend.mux_sched = function(panel)
tvheadend.mux_sched = function(panel, index)
{
tvheadend.idnode_grid(panel, {
url: 'api/mpegts/mux_sched',
comet: 'mpegts_mux_sched',
titleS: 'Mux Scheduler',
titleP: 'Mux Schedulers',
tabIndex: 4,
tabIndex: index,
help: function() {
new tvheadend.help('Mux Schedulers', 'config_muxsched.html');
},

View file

@ -4,10 +4,8 @@
tvheadend.service_mapper_status_panel = null;
tvheadend.service_mapper_status = function()
tvheadend.service_mapper_status = function(panel, index)
{
var panel;
/* Fields */
var ok = new Ext.form.Label({
fieldLabel: 'Mapped',
@ -31,7 +29,7 @@ tvheadend.service_mapper_status = function()
});
/* Panel */
panel = new Ext.FormPanel({
var mpanel = new Ext.FormPanel({
method: 'get',
title: 'Service Mapper',
frame: true,
@ -72,9 +70,9 @@ tvheadend.service_mapper_status = function()
}
});
tvheadend.service_mapper_status_panel = panel;
return panel;
};
tvheadend.service_mapper_status_panel = mpanel;
tvheadend.paneladd(panel, mpanel, index);
}
/*
* Start mapping
@ -182,4 +180,4 @@ tvheadend.service_mapper = function(t, e, store, select)
});
win.show();
};
}

View file

@ -1,52 +1,19 @@
/**
*
*/
tvheadend.status_subs = function() {
tvheadend.subsStore = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [{
name: 'id'
}, {
name: 'hostname'
}, {
name: 'username'
}, {
name: 'title'
}, {
name: 'channel'
}, {
name: 'service'
}, {
name: 'state'
}, {
name: 'errors'
}, {
name: 'in'
}, {
name: 'out'
}, {
name: 'start',
type: 'date',
dateFormat: 'U' /* unix time */
}],
url: 'api/status/subscriptions',
autoLoad: true,
id: 'id'
});
tvheadend.comet.on('subscriptions', function(m) {
tvheadend.status_subs = function(panel, index)
{
var subs = null;
var store = null;
function update(m) {
if (m.reload != null)
tvheadend.subsStore.reload();
store.reload();
if (m.updateEntry != null) {
r = tvheadend.subsStore.getById(m.id);
r = store.getById(m.id);
if (typeof r === 'undefined') {
tvheadend.subsStore.reload();
store.reload();
return;
}
@ -57,378 +24,495 @@ tvheadend.status_subs = function() {
r.data.in = m.in;
r.data.out = m.out;
tvheadend.subsStore.afterEdit(r);
tvheadend.subsStore.fireEvent('updated', tvheadend.subsStore, r,
Ext.data.Record.COMMIT);
store.afterEdit(r);
store.fireEvent('updated', store, r, Ext.data.Record.COMMIT);
}
});
function renderDate(value) {
var dt = new Date(value);
return dt.format('D j M H:i');
}
function renderBw(value, item, store) {
var txt = parseInt(value / 125);
var href = 'javascript:tvheadend.subscription_bw_monitor(' + store.id + ');';
return '<a href="' + href + '">' + txt + '</a>';
function builder() {
if (subs)
return;
store = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [
{ name: 'id' },
{ name: 'hostname' },
{ name: 'username' },
{ name: 'title' },
{ name: 'channel' },
{ name: 'service' },
{ name: 'state' },
{ name: 'errors' },
{ name: 'in' },
{ name: 'out' },
{
name: 'start',
type: 'date',
dateFormat: 'U' /* unix time */
}
],
url: 'api/status/subscriptions',
autoLoad: true,
id: 'id'
});
tvheadend.subsStore = store;
tvheadend.comet.on('subscriptions', update);
function renderBw(value, item, record) {
var txt = parseInt(value / 125);
var href = 'javascript:tvheadend.subscription_bw_monitor(' + record.id + ');';
return '<a href="' + href + '">' + txt + '</a>';
}
var subsCm = new Ext.grid.ColumnModel([
{
width: 50,
id: 'hostname',
header: "Hostname",
dataIndex: 'hostname'
},
{
width: 50,
id: 'username',
header: "Username",
dataIndex: 'username'
},
{
width: 80,
id: 'title',
header: "Title",
dataIndex: 'title'
},
{
width: 50,
id: 'channel',
header: "Channel",
dataIndex: 'channel'
},
{
width: 200,
id: 'service',
header: "Service",
dataIndex: 'service'
},
{
width: 50,
id: 'start',
header: "Start",
dataIndex: 'start',
renderer: function(v) {
var dt = new Date(v);
return dt.format('D j M H:i');
}
},
{
width: 50,
id: 'state',
header: "State",
dataIndex: 'state'
},
{
width: 50,
id: 'errors',
header: "Errors",
dataIndex: 'errors'
},
{
width: 50,
id: 'in',
header: "Input (kb/s)",
dataIndex: 'in',
renderer: renderBw,
},
{
width: 50,
id: 'out',
header: "Output (kb/s)",
dataIndex: 'out',
renderer: renderBw
}
]);
subs = new Ext.grid.GridPanel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
store: store,
cm: subsCm,
flex: 1,
viewConfig: {
forceFit: true
}
});
dpanel.add(subs);
dpanel.doLayout(false, true);
}
function destroyer() {
if (subs === null || !tvheadend.dynamic)
return;
dpanel.removeAll()
tvheadend.subsStore = null;
store.destroy();
store = null;
subs = null;
}
var subsCm = new Ext.grid.ColumnModel([{
width: 50,
id: 'hostname',
header: "Hostname",
dataIndex: 'hostname'
}, {
width: 50,
id: 'username',
header: "Username",
dataIndex: 'username'
}, {
width: 80,
id: 'title',
header: "Title",
dataIndex: 'title'
}, {
width: 50,
id: 'channel',
header: "Channel",
dataIndex: 'channel'
}, {
width: 200,
id: 'service',
header: "Service",
dataIndex: 'service'
}, {
width: 50,
id: 'start',
header: "Start",
dataIndex: 'start',
renderer: renderDate
}, {
width: 50,
id: 'state',
header: "State",
dataIndex: 'state'
}, {
width: 50,
id: 'errors',
header: "Errors",
dataIndex: 'errors'
}, {
width: 50,
id: 'in',
header: "Input (kb/s)",
dataIndex: 'in',
renderer: renderBw
}, {
width: 50,
id: 'out',
header: "Output (kb/s)",
dataIndex: 'out',
renderer: renderBw
}]);
var subs = new Ext.grid.GridPanel({
var dpanel = new Ext.Panel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
header: false,
layout: 'fit',
title: 'Subscriptions',
iconCls: 'eye',
store: tvheadend.subsStore,
cm: subsCm,
flex: 1,
viewConfig: {
forceFit: true
}
iconCls: 'eye'
});
return subs;
tvheadend.paneladd(panel, dpanel, index);
tvheadend.panelreg(panel, dpanel, builder, destroyer);
};
/**
* Streams
*/
tvheadend.status_streams = function() {
tvheadend.status_streams = function(panel, index)
{
var grid = null;
var store = null;
tvheadend.streamStatusStore = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [{
name: 'uuid'
}, {
name: 'input'
}, {
name: 'username'
}, {
name: 'stream'
}, {
name: 'subs'
}, {
name: 'weight'
}, {
name: 'signal'
}, {
name: 'ber'
}, {
name: 'unc'
}, {
name: 'snr'
}, {
name: 'bps'
}, {
name: 'cc'
}, {
name: 'te'
}, {
name: 'signal_scale'
}, {
name: 'snr_scale'
}, {
name: 'ec_bit'
}, {
name: 'tc_bit'
}, {
name: 'ec_block'
}, {
name: 'tc_block'
}
],
url: 'api/status/inputs',
autoLoad: true,
id: 'uuid'
});
function update(m) {
if (m.reload != null) {
store.reload();
return;
}
if (m.update == null)
return;
var r = store.getById(m.uuid);
if (!r) {
store.reload();
return;
}
r.data.subs = m.subs;
r.data.weight = m.weight;
r.data.signal = m.signal;
r.data.ber = m.ber;
r.data.unc = m.unc;
r.data.snr = m.snr;
r.data.bps = m.bps;
r.data.cc = m.cc;
r.data.te = m.te;
r.data.signal_scale = m.signal_scale;
r.data.snr_scale = m.snr_scale;
r.data.ec_bit = m.ec_bit;
r.data.tc_bit = m.tc_bit;
r.data.ec_block = m.ec_block;
r.data.tc_block = m.tc_block;
function renderBw(value, item, store) {
var txt = parseInt(value / 1024);
var href = "javascript:tvheadend.stream_bw_monitor('" + store.id + "');";
return '<a href="' + href + '">' + txt + '</a>';
store.afterEdit(r);
store.fireEvent('updated', store, Ext.data.Record.COMMIT);
}
function renderBer(value, item, store) {
if (store.data.tc_bit == 0)
return value; // fallback (driver/vendor dependent ber)
function builder() {
if (grid)
return;
// ber = error_bit_count / total_bit_count
var ber = store.data.ec_bit / store.data.tc_bit;
return ber;
store = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [
{ name: 'uuid' },
{ name: 'input' },
{ name: 'username' },
{ name: 'stream' },
{ name: 'subs' },
{ name: 'weight' },
{ name: 'signal' },
{ name: 'ber' },
{ name: 'unc' },
{ name: 'snr' },
{ name: 'bps' },
{ name: 'cc' },
{ name: 'te' },
{ name: 'signal_scale' },
{ name: 'snr_scale' },
{ name: 'ec_bit' },
{ name: 'tc_bit' },
{ name: 'ec_block' },
{ name: 'tc_block' }
],
url: 'api/status/inputs',
autoLoad: true,
id: 'uuid'
});
tvheadend.streamStatusStore = store;
tvheadend.comet.on('input_status', update);
function renderBw(value, item, record) {
var txt = parseInt(value / 1024);
var href = "javascript:tvheadend.stream_bw_monitor('" + record.id + "');";
return '<a href="' + href + '">' + txt + '</a>';
}
function renderBer(value, item, store) {
if (store.data.tc_bit == 0)
return value; // fallback (driver/vendor dependent ber)
// ber = error_bit_count / total_bit_count
var ber = store.data.ec_bit / store.data.tc_bit;
return ber;
}
function renderPer(value, item, store) {
if (value == 0) // value: total_block_count
return '<span class="tvh-grid-unset">Unknown</span>';
// per = error_block_count / total_block_count
var per = store.data.ec_block / value;
return per;
}
var cm = new Ext.grid.ColumnModel([
{
width: 120,
header: "Input",
dataIndex: 'input'
},
{
width: 100,
header: "Stream",
dataIndex: 'stream'
},
{
width: 50,
header: "Subs #",
dataIndex: 'subs'
},
{
width: 50,
header: "Weight",
dataIndex: 'weight'
},
{
width: 50,
header: "Bandwidth (kb/s)",
dataIndex: 'bps',
renderer: renderBw
},
{
width: 50,
header: "BER",
dataIndex: 'ber',
renderer: renderBer
},
{
width: 50,
header: "PER",
dataIndex: 'tc_block',
renderer: renderPer
},
{
width: 50,
header: "Uncorrected Blocks",
dataIndex: 'unc'
},
{
width: 50,
header: "Transport Errors",
dataIndex: 'te'
},
{
width: 50,
header: "Continuity Errors",
dataIndex: 'cc'
}
]);
cm.config.push(new Ext.ux.grid.ProgressColumn({
header: "SNR",
dataIndex: 'snr',
width: 85,
colored: true,
ceiling: 65535,
tvh_renderer: function(v, p, record) {
var scale = record.get('snr_scale');
if (scale == 1)
return v;
if (scale == 2 && v > 0) {
var snr = v * 0.0001;
return snr.toFixed(1) + " dB";
}
return '<span class="tvh-grid-unset">Unknown</span>';
},
destroy: function() {
}
}));
cm.config.push(new Ext.ux.grid.ProgressColumn({
header: "Signal Strength",
dataIndex: 'signal',
width: 85,
colored: true,
ceiling: 65535,
tvh_renderer: function(v, p, record) {
var scale = record.get('snr_scale');
if (scale == 1)
return v;
if (scale == 2 && v > 0) {
var snr = v * 0.0001;
return snr.toFixed(1) + " dBm";
}
return '<span class="tvh-grid-unset">Unknown</span>';
},
destroy: function() {
}
}));
grid = new Ext.grid.GridPanel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
store: store,
cm: cm,
flex: 1,
viewConfig: {
forceFit: true
}
});
dpanel.add(grid);
dpanel.doLayout(false, true);
}
function renderPer(value, item, store) {
if (value == 0) // value: total_block_count
return '<span class="tvh-grid-unset">Unknown</span>';
// per = error_block_count / total_block_count
var per = store.data.ec_block / value;
return per;
function destroyer() {
if (grid === null || !tvheadend.dynamic)
return;
dpanel.removeAll()
tvheadend.streamStatusStore = null;
store.destroy();
store = null;
grid = null;
}
var cm = new Ext.grid.ColumnModel([{
width: 120,
header: "Input",
dataIndex: 'input'
}, {
width: 100,
header: "Stream",
dataIndex: 'stream'
}, {
width: 50,
header: "Subs #",
dataIndex: 'subs'
}, {
width: 50,
header: "Weight",
dataIndex: 'weight'
}, {
width: 50,
header: "Bandwidth (kb/s)",
dataIndex: 'bps',
renderer: renderBw
}, {
width: 50,
header: "BER",
dataIndex: 'ber',
renderer: renderBer
}, {
width: 50,
header: "PER",
dataIndex: 'tc_block',
renderer: renderPer
}, {
width: 50,
header: "Uncorrected Blocks",
dataIndex: 'unc'
}, {
width: 50,
header: "Transport Errors",
dataIndex: 'te'
}, {
width: 50,
header: "Continuity Errors",
dataIndex: 'cc'
}]);
cm.config.push(new Ext.ux.grid.ProgressColumn({
header: "SNR",
dataIndex: 'snr',
width: 85,
colored: true,
ceiling: 65535,
tvh_renderer: function(v, p, record) {
var scale = record.get('snr_scale');
if (scale == 1)
return v;
if (scale == 2 && v > 0) {
var snr = v * 0.0001;
return snr.toFixed(1) + " dB";
}
return '<span class="tvh-grid-unset">Unknown</span>';
}
}));
cm.config.push(new Ext.ux.grid.ProgressColumn({
header: "Signal Strength",
dataIndex: 'signal',
width: 85,
colored: true,
ceiling: 65535,
tvh_renderer: function(v, p, record) {
var scale = record.get('snr_scale');
if (scale == 1)
return v;
if (scale == 2 && v > 0) {
var snr = v * 0.0001;
return snr.toFixed(1) + " dBm";
}
return '<span class="tvh-grid-unset">Unknown</span>';
}
}));
tvheadend.comet.on('input_status', function(m) {
if (m.reload != null)
tvheadend.streamStatusStore.reload();
if (m.update != null) {
var r = tvheadend.streamStatusStore.getById(m.uuid);
if (r) {
r.data.subs = m.subs;
r.data.weight = m.weight;
r.data.signal = m.signal;
r.data.ber = m.ber;
r.data.unc = m.unc;
r.data.snr = m.snr;
r.data.bps = m.bps;
r.data.cc = m.cc;
r.data.te = m.te;
r.data.signal_scale = m.signal_scale;
r.data.snr_scale = m.snr_scale;
r.data.ec_bit = m.ec_bit;
r.data.tc_bit = m.tc_bit;
r.data.ec_block = m.ec_block;
r.data.tc_block = m.tc_block;
tvheadend.streamStatusStore.afterEdit(r);
tvheadend.streamStatusStore.fireEvent('updated',
tvheadend.streamStatusStore,
r,
Ext.data.Record.COMMIT);
} else {
tvheadend.streamStatusStore.reload();
}
}
});
var panel = new Ext.grid.GridPanel({
var dpanel = new Ext.Panel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
header: false,
layout: 'fit',
title: 'Stream',
iconCls: 'hardware',
store: tvheadend.streamStatusStore,
cm: cm,
flex: 1,
viewConfig: {
forceFit: true
}
iconCls: 'hardware'
});
return panel;
tvheadend.paneladd(panel, dpanel, index);
tvheadend.panelreg(panel, dpanel, builder, destroyer);
};
/**
*
*/
tvheadend.status_conns = function() {
tvheadend.status_conns = function(panel, index) {
var store = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [{
name: 'id'
}, {
name: 'type'
}, {
name: 'peer'
}, {
name: 'user'
}, {
name: 'started',
type: 'date',
dateFormat: 'U' /* unix time */
}],
url: 'api/status/connections',
autoLoad: true,
id: 'id'
});
var grid = null;
var store = null;
tvheadend.comet.on('connections', function(m) {
function update(m) {
if (m.reload != null)
store.reload();
});
function renderDate(value) {
var dt = new Date(value);
return dt.format('Y-m-d H:i:s');
}
var cm = new Ext.grid.ColumnModel([{
width: 50,
id: 'type',
header: "Type",
dataIndex: 'type'
}, {
width: 50,
id: 'peer',
header: "IP Address",
dataIndex: 'peer'
}, {
width: 50,
id: 'user',
header: "Username",
dataIndex: 'user'
}, {
width: 50,
id: 'started',
header: "Started",
dataIndex: 'started',
renderer: renderDate
}]);
function builder() {
if (grid)
return;
var panel = new Ext.grid.GridPanel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
title: 'Connections',
iconCls: 'eye',
store: store,
cm: cm,
flex: 1,
viewConfig: {
forceFit: true
store = new Ext.data.JsonStore({
root: 'entries',
totalProperty: 'totalCount',
fields: [
{ name: 'id' },
{ name: 'type' },
{ name: 'peer' },
{ name: 'user' },
{
name: 'started',
type: 'date',
dateFormat: 'U' /* unix time */
}
],
url: 'api/status/connections',
autoLoad: true,
id: 'id'
});
tvheadend.comet.on('connections', update);
function renderDate(value) {
var dt = new Date(value);
return dt.format('Y-m-d H:i:s');
}
var cm = new Ext.grid.ColumnModel([{
width: 50,
id: 'type',
header: "Type",
dataIndex: 'type'
}, {
width: 50,
id: 'peer',
header: "IP Address",
dataIndex: 'peer'
}, {
width: 50,
id: 'user',
header: "Username",
dataIndex: 'user'
}, {
width: 50,
id: 'started',
header: "Started",
dataIndex: 'started',
renderer: renderDate
}]);
grid = new Ext.grid.GridPanel({
border: false,
loadMask: true,
stripeRows: true,
disableSelection: true,
store: store,
cm: cm,
flex: 1,
viewConfig: {
forceFit: true
}
});
dpanel.add(grid);
dpanel.doLayout(false, true);
}
function destroyer() {
if (grid === null || !tvheadend.dynamic)
return;
dpanel.removeAll()
store.destroy();
store = null;
grid = null;
}
var dpanel = new Ext.Panel({
border: false,
header: false,
layout: 'fit',
title: 'Connections',
iconCls: 'eye'
});
return panel;
tvheadend.paneladd(panel, dpanel, index);
tvheadend.panelreg(panel, dpanel, builder, destroyer);
};
tvheadend.status = function() {
@ -437,13 +521,12 @@ tvheadend.status = function() {
autoScroll: true,
activeTab: 0,
iconCls: 'eye',
items: [
new tvheadend.status_streams,
new tvheadend.status_subs,
new tvheadend.status_conns,
new tvheadend.service_mapper_status
]
items: [],
});
tvheadend.status_streams(panel);
tvheadend.status_subs(panel);
tvheadend.status_conns(panel);
tvheadend.service_mapper_status(panel);
return panel;
};
@ -517,8 +600,9 @@ tvheadend.subscription_bw_monitor = function(id) {
var task = {
interval: 1000,
run: function() {
r = tvheadend.subsStore.getById(id);
if (typeof r === 'undefined') {
var store = tvheadend.subsStore;
var r = store ? store.getById(id) : null;
if (!store || typeof r === 'undefined') {
chart.stop();
Ext.TaskMgr.stop(task);
return;
@ -607,10 +691,11 @@ tvheadend.stream_bw_monitor = function(id) {
});
var task = {
interval: 1000,
interval: 10000,
run: function() {
r = tvheadend.streamStatusStore.getById(id);
if (typeof r === 'undefined') {
var store = tvheadend.streamStatusStore;
var r = store ? store.getById(id) : null;
if (!store || typeof r === 'undefined') {
chart.stop();
Ext.TaskMgr.stop(task);
return;

View file

@ -1,4 +1,4 @@
tvheadend.timeshift = function() {
tvheadend.timeshift = function(panel, index) {
/* ****************************************************************
* Data
@ -177,5 +177,5 @@ tvheadend.timeshift = function() {
});
}
return confpanel;
tvheadend.paneladd(panel, confpanel, index);
};

View file

@ -1,10 +1,13 @@
tvheadend.tvadapters = function() {
return tvheadend.idnode_tree({
tvheadend.tvadapters = function(panel, index) {
tvheadend.idnode_tree(panel, {
url: 'api/hardware/tree',
title: 'TV adapters',
comet: 'hardware',
tabIndex: index,
help: function() {
new tvheadend.help('TV adapters', 'config_tvadapters.html');
}
});
return panel;
};

View file

@ -1,9 +1,8 @@
tvheadend.dynamic = true;
tvheadend.accessupdate = null;
tvheadend.capabilties = null;
tvheadend.conf_chepg = null;
tvheadend.conf_dvbin = null;
tvheadend.conf_tsdvr = null;
tvheadend.conf_csa = null;
tvheadend.dvrpanel = null;
tvheadend.confpanel = null;
/* State Provider */
Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
@ -40,6 +39,100 @@ tvheadend.help = function(title, pagename) {
});
};
tvheadend.paneladd = function(dst, add, idx) {
if (idx != null)
dst.insert(idx, add);
else
dst.add(add);
};
tvheadend.panelreg = function(tabpanel, panel, builder, destroyer) {
/* the 'activate' event does not work in ExtJS 3.4 */
tabpanel.on('beforetabchange', function(tp, p) {
if (p == panel)
builder();
});
panel.on('deactivate', destroyer);
}
tvheadend.Ajax = function(conf) {
var orig_success = conf.success;
var orig_failure = conf.failure;
conf.success = function(d) {
tvheadend.loading(0);
if (orig_success)
orig_success(d);
}
conf.failure = function(d) {
tvheadend.loading(0);
if (orig_failure)
orig_failure(d);
}
tvheadend.loading(1);
Ext.Ajax.request(conf);
};
tvheadend.AjaxConfirm = function(conf) {
Ext.MessageBox.confirm(
conf.title || 'Message',
conf.question || 'Do you really want to delete the selection?',
function (btn) {
if (btn == 'yes')
tvheadend.Ajax(conf);
}
);
};
tvheadend.loading = function(on) {
if (on)
Ext.getBody().mask('Loading... Please, wait...', 'loading');
else
Ext.getBody().unmask();
};
/*
* Any Match option in ComboBox queries
* This query is identical as in extjs-all.js
* except one
*/
tvheadend.doQueryAnyMatch = function(q, forceAll) {
q = Ext.isEmpty(q) ? '' : q;
var qe = {
query: q,
forceAll: forceAll,
combo: this,
cancel:false
};
if (this.fireEvent('beforequery', qe) === false || qe.cancel)
return false;
q = qe.query;
forceAll = qe.forceAll;
if (forceAll === true || (q.length >= this.minChars)) {
if (this.lastQuery !== q) {
this.lastQuery = q;
if (this.mode == 'local') {
this.selectedIndex = -1;
if (forceAll) {
this.store.clearFilter();
} else {
/* supply the anyMatch option (last param) */
this.store.filter(this.displayField, q, true);
}
this.onLoad();
} else {
this.store.baseParams[this.queryParam] = q;
this.store.load({ params: this.getParams(q) });
this.expand();
}
} else {
this.selectedIndex = -1;
this.onLoad();
}
}
}
/*
* General capabilities
*/
@ -218,109 +311,119 @@ function accessUpdate(o) {
if (!tvheadend.capabilities)
return;
tvheadend.rootTabPanel.setLogin(o.username);
if (tvheadend.autorecButton)
tvheadend.autorecButton.setDisabled(o.dvr != true);
if (o.dvr == true && tvheadend.dvrpanel == null) {
tvheadend.dvrpanel = new tvheadend.dvr;
tvheadend.dvrpanel = tvheadend.dvr();
tvheadend.rootTabPanel.add(tvheadend.dvrpanel);
}
if (o.admin == true && tvheadend.confpanel == null) {
var tabs1 = [
new tvheadend.miscconf,
new tvheadend.acleditor
];
var tabs2;
/* DVB inputs */
tabs2 = [];
if (tvheadend.capabilities.indexOf('linuxdvb') !== -1 ||
tvheadend.capabilities.indexOf('satip_client') !== -1 ||
tvheadend.capabilities.indexOf('v4l') !== -1) {
tabs2.push(new tvheadend.tvadapters);
}
/*
tabs2.push(new tvheadend.iptv);
*/
tvheadend.conf_dvbin = new Ext.TabPanel({
var cp = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Configuration',
iconCls: 'wrench',
items: []
});
tvheadend.miscconf(cp);
tvheadend.acleditor(cp);
/* DVB inputs, networks, muxes, services */
var dvbin = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'DVB Inputs',
iconCls: 'hardware',
items: tabs2
items: []
});
tvheadend.networks(tvheadend.conf_dvbin);
tvheadend.muxes(tvheadend.conf_dvbin);
tvheadend.services(tvheadend.conf_dvbin);
tvheadend.mux_sched(tvheadend.conf_dvbin);
tabs1.push(tvheadend.conf_dvbin);
var idx = 0;
if (tvheadend.capabilities.indexOf('linuxdvb') !== -1 ||
tvheadend.capabilities.indexOf('satip_client') !== -1 ||
tvheadend.capabilities.indexOf('v4l') !== -1)
tvheadend.tvadapters(dvbin);
tvheadend.networks(dvbin);
tvheadend.muxes(dvbin);
tvheadend.services(dvbin);
tvheadend.mux_sched(dvbin);
cp.add(dvbin);
/* Channel / EPG */
tvheadend.conf_chepg = new Ext.TabPanel({
var chepg = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Channel / EPG',
iconCls: 'television',
items: []
});
tvheadend.channel_tab(tvheadend.conf_chepg, 0);
tvheadend.cteditor(tvheadend.conf_chepg, 1);
tvheadend.conf_chepg.insert(2, new tvheadend.epggrab);
tabs1.push(tvheadend.conf_chepg);
tvheadend.channel_tab(chepg);
tvheadend.cteditor(chepg);
tvheadend.epggrab(chepg);
cp.add(chepg);
/* DVR / Timeshift */
tabs2 = [new tvheadend.dvrsettings];
if (tvheadend.capabilities.indexOf('timeshift') !== -1) {
tabs2.push(new tvheadend.timeshift);
}
tvheadend.conf_tsdvr = new Ext.TabPanel({
var tsdvr = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Recording',
iconCls: 'drive',
items: tabs2
items: []
});
tabs1.push(tvheadend.conf_tsdvr);
tvheadend.dvr_settings(tsdvr);
if (tvheadend.capabilities.indexOf('timeshift') !== -1)
tvheadend.timeshift(tsdvr);
cp.add(tsdvr);
/* CSA */
tabs2 = [];
if (tvheadend.capabilities.indexOf('cwc') !== -1)
tabs2.push(new tvheadend.cwceditor);
if (tvheadend.capabilities.indexOf('capmt') !== -1)
tabs2.push(new tvheadend.capmteditor);
if (tabs2.length > 0) {
tvheadend.conf_csa = new Ext.TabPanel({
if (tvheadend.capabilities.indexOf('cwc') !== -1 ||
tvheadend.capabilities.indexOf('capmt') !== -1) {
var csa = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'CSA',
iconCls: 'key',
items: tabs2
items: []
});
tabs1.push(tvheadend.conf_csa);
if (tvheadend.capabilities.indexOf('cwc') !== -1)
tvheadend.cwceditor(csa);
if (tvheadend.capabilities.indexOf('capmt') !== -1)
tvheadend.capmteditor(csa);
cp.add(csa);
}
/* Stream Config */
tvheadend.conf_stream = new Ext.TabPanel({
var stream = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Stream',
iconCls: 'stream_config',
items: []
});
tvheadend.esfilter_tab(tvheadend.conf_stream);
tabs1.push(tvheadend.conf_stream);
tvheadend.esfilter_tab(stream);
cp.add(stream);
/* Debug */
tabs1.push(new tvheadend.tvhlog);
tvheadend.tvhlog(cp);
tvheadend.confpanel = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Configuration',
iconCls: 'wrench',
items: tabs1
});
tvheadend.rootTabPanel.add(tvheadend.confpanel);
tvheadend.confpanel.doLayout();
/* Finish */
tvheadend.rootTabPanel.add(cp);
tvheadend.confpanel = cp;
cp.doLayout();
}
if (o.admin == true && tvheadend.statuspanel == null) {
@ -365,6 +468,86 @@ tvheadend.log = function(msg, style) {
e.scrollIntoView('systemlog');
};
/**
*
*/
tvheadend.RootTabPanel = Ext.extend(Ext.TabPanel, {
onRender: function(ct, position) {
tvheadend.RootTabPanel.superclass.onRender.call(this, ct, position);
/* Create login components */
var before = this.strip.dom.childNodes[this.strip.dom.childNodes.length-1];
if (!this.loginTpl) {
var tt = new Ext.Template(
'<li class="x-tab-login" id="{id}">',
'<span class="x-tab-strip-login {iconCls}">{text}</span></li>'
);
tt.disableFormats = true;
tt.compile();
tvheadend.RootTabPanel.prototype.loginTpl = tt;
}
var item = new Ext.Component();
var p = this.getTemplateArgs(item);
var before = this.strip.dom.childNodes[this.strip.dom.childNodes.length-1];
item.tabEl = this.loginTpl.insertBefore(before, p);
this.loginItem = item;
if (!this.loginCmdTpl) {
var tt = new Ext.Template(
'<li class="x-tab-login" id="{id}"><a href="#">',
'<span class="x-tab-strip-login-cmd"></span></a></li>'
);
tt.disableFormats = true;
tt.compile();
tvheadend.RootTabPanel.prototype.loginCmdTpl = tt;
}
var item = new Ext.Component();
var p = this.getTemplateArgs(item);
var el = this.loginCmdTpl.insertBefore(before, p);
item.tabEl = Ext.get(el);
item.tabEl.select('a').on('click', this.onLoginCmdClicked, this, {preventDefault: true});
this.loginCmdItem = item;
this.on('beforetabchange', function(tp, p) {
if (p == this.loginItem || p == this.loginCmdItem)
return false;
});
this.setLogin('');
},
getComponent: function(comp) {
if (comp === this.loginItem.id || comp == this.loginItem)
return this.loginItem;
if (comp === this.loginCmdItem.id || comp == this.loginCmdItem)
return this.loginCmdItem;
return tvheadend.RootTabPanel.superclass.getComponent.call(this, comp);
},
setLogin: function(login) {
this.login = login;
if (login) {
text = 'Logged in as <b>' + login + '</b>';
cmd = '(logout)';
} else {
text = 'No verified access';
cmd = '(login)';
}
var el = this.loginItem.tabEl;
var fly = Ext.fly(this.loginItem.tabEl);
var t = fly.child('span.x-tab-strip-login', true);
Ext.fly(this.loginItem.tabEl).child('span.x-tab-strip-login', true).innerHTML = text;
Ext.fly(this.loginCmdItem.tabEl).child('span.x-tab-strip-login-cmd', true).innerHTML = cmd;
},
onLoginCmdClicked: function(e) {
window.location.href = this.login ? 'logout' : 'login';
}
});
/**
*
*/
@ -386,10 +569,10 @@ tvheadend.app = function() {
html: '<div id="header"><h1>Tvheadend Web-Panel</h1></div>'
});
tvheadend.rootTabPanel = new Ext.TabPanel({
tvheadend.rootTabPanel = new tvheadend.RootTabPanel({
region: 'center',
activeTab: 0,
items: [new tvheadend.epg]
items: [tvheadend.epg()]
});
var viewport = new Ext.Viewport({
@ -439,4 +622,3 @@ tvheadend.app = function() {
};
}(); // end of app

View file

@ -1,4 +1,4 @@
tvheadend.tvhlog = function() {
tvheadend.tvhlog = function(panel, index) {
/*
* Basic Config
*/
@ -115,5 +115,5 @@ tvheadend.tvhlog = function() {
});
}
return confpanel;
tvheadend.paneladd(panel, confpanel, index);
};

Some files were not shown because too many files have changed in this diff Show more