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 \
#include "access.h"
#include "settings.h"
#include "channels.h"
+#include "tcp.h"
struct access_entry_queue access_entries;
struct access_ticket_queue access_tickets;
void
access_destroy(access_t *a)
{
+ free(a->aa_username);
+ free(a->aa_representative);
htsmsg_destroy(a->aa_chtags);
free(a);
}
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;
/**
*
*/
-static void
+void
access_entry_save(access_entry_t *ae)
{
htsmsg_t *c = htsmsg_create_map();
} access_ticket_t;
typedef struct access {
+ char *aa_username;
+ char *aa_representative;
uint32_t aa_rights;
uint32_t aa_chmin;
uint32_t aa_chmax;
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
*/
access_entry_t *
access_entry_create(const char *uuid, htsmsg_t *conf);
+/**
+ *
+ */
+void
+access_entry_save(access_entry_t *ae);
+
/**
*
*/
}
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;
// 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);
api_esfilter_init();
api_intlconv_init();
api_access_init();
+ api_dvr_init();
}
void api_done ( void )
#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
{
/*
* 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
void api_esfilter_init ( void );
void api_intlconv_init ( void );
void api_access_init ( void );
+void api_dvr_init ( void );
/*
* IDnode
} 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
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;
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;
// 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;
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;
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;
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;
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;
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;
--- /dev/null
+/*
+ * 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;
+
+ lock_assert(&global_lock);
+
+ if (access_verify2(perm, ACCESS_RECORDER_ALL))
+ return config_uuid; /* no change */
+
+ cfg = dvr_config_find_by_name(perm->aa_username);
+ if (cfg)
+ return cfg->dvr_config_name;
+
+ if (perm->aa_username)
+ tvhlog(LOG_INFO, "dvr", "User '%s' has no dvr config with identical name, using default...", perm->aa_username);
+
+ return NULL; /* default */
+}
+
+/*
+ *
+ */
+
+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;
+ if (access_verify2(perm, ACCESS_RECORDER_ALL | ACCESS_RECORDER))
+ return EACCES;
+
+ 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;
+
+ if (!(conf = htsmsg_get_map(args, "conf")))
+ return EINVAL;
+
+ if (access_verify2(perm, ACCESS_RECORDER_ALL)) {
+ htsmsg_delete_field(conf, "config_name");
+ htsmsg_add_str(conf, "config_name", perm->aa_username ?: "");
+ }
+
+ pthread_mutex_lock(&global_lock);
+ 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;
+
+ htsmsg_delete_field(conf, "creator");
+ if (perm->aa_representative)
+ htsmsg_add_str(conf, "creator", perm->aa_representative);
+
+ pthread_mutex_lock(&global_lock);
+ dae = dvr_autorec_create(NULL, conf);
+ if (dae)
+ dvr_autorec_save(dae);
+ 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);
+ }
+ 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);
+}
/* 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)))
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;
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 },
};
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);
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;
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;
#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);
#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 },
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;
/* Create list */
pthread_mutex_lock(&global_lock);
- cb(&ins, &conf, args);
+ cb(perm, &ins, &conf, args);
/* Sort */
if (conf.sort.key)
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--;
}
/* 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;
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();
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);
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;
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 */
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);
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);
+ }
}
}
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;
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;
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?
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;
/* 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);
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);
}
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;
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 */
if (!(in = idnode_find(uuid, NULL)))
err = ENOENT;
else
- handler(in);
+ handler(perm, in);
}
pthread_mutex_unlock(&global_lock);
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(perm, args, resp, api_idnode_delete_);
+}
+
+static void
+api_idnode_moveup_ (access_t *perm, idnode_t *in)
{
- return api_idnode_handler(args, resp, idnode_delete);
+ 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(perm, args, resp, api_idnode_moveup_);
+}
+
+static void
+api_idnode_movedown_ (access_t *perm, idnode_t *in)
{
- return api_idnode_handler(args, resp, idnode_moveup);
+ 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 )
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);
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))
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;
}
*/
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;
*/
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;
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;
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;
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;
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;
*/
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;
*/
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;
*/
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)
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;
#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");
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;
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();
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();
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;
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;
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;
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();
* 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,
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
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)) {
+ if (u32 == 0)
+ u32 = -1;
+ htsmsg_delete_field(c, "approx_time");
+ htsmsg_add_u32(c, "start", u32);
+ }
+ 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);
+ }
+}
+
/*
* Migration table
*/
config_migrate_v6,
config_migrate_v7,
config_migrate_v8,
+ config_migrate_v9,
};
/*
#include "lang_str.h"
typedef struct dvr_config {
+ idnode_t dvr_id;
+ LIST_ENTRY(dvr_config) config_link;
+
+ int dvr_enabled;
char *dvr_config_name;
char *dvr_storage;
uint32_t dvr_retention_days;
char *dvr_charset;
char *dvr_charset_id;
char *dvr_postproc;
- int dvr_extra_time_pre;
- int dvr_extra_time_post;
-
- muxer_container_type_t dvr_mc;
- muxer_config_t dvr_muxcnf;
+ uint32_t dvr_extra_time_pre;
+ uint32_t dvr_extra_time_post;
+
+ int dvr_mc;
+ muxer_config_t dvr_muxcnf;
+
+ int dvr_dir_per_day;
+ int dvr_channel_dir;
+ int dvr_channel_in_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;
+ int dvr_rewrite_pat;
+ int dvr_rewrite_pmt;
/* Series link support */
int dvr_sl_brand_lock;
/* Duplicate detect */
int dvr_dup_detect_episode;
- LIST_ENTRY(dvr_config) config_link;
} dvr_config_t;
extern struct dvr_config_list dvrconfigs;
typedef struct dvr_entry {
+ idnode_t de_id;
+
int de_refcnt; /* Modification is protected under global_lock */
*/
LIST_ENTRY(dvr_entry) de_global_link;
- int de_id;
channel_t *de_channel;
LIST_ENTRY(dvr_entry) de_channel_link;
* These meta fields will stay valid as long as reference count > 0
*/
+ dvr_config_t *de_config;
char *de_config_name;
time_t de_start;
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
* Autorec entry
*/
typedef struct dvr_autorec_entry {
+ idnode_t dae_id;
+
TAILQ_ENTRY(dvr_autorec_entry) dae_link;
- char *dae_id;
+ char *dae_name;
char *dae_config_name;
int dae_enabled;
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;
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
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_config_save(dvr_config_t *cfg);
+
+int dvr_entry_get_mc( dvr_entry_t *de);
+
void dvr_entry_notify(dvr_entry_t *de);
void dvr_entry_save(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);
-
-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_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_entry_created(dvr_entry_t *de);
+
+dvr_entry_t *
+dvr_entry_create ( const char *uuid, htsmsg_t *conf );
+
+
+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);
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);
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);
+
/**
* Query interface
*/
/**
*
*/
-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);
+
+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);
void autorec_destroy_by_channel(channel_t *ch, int delconf);
-dvr_autorec_entry_t *autorec_entry_find(const char *id, int create);
-
/**
*
*/
#include "dtable.h"
#include "epg.h"
-dtable_t *autorec_dt;
-
-TAILQ_HEAD(dvr_autorec_entry_queue, dvr_autorec_entry);
-
static int dvr_autorec_in_init = 0;
struct dvr_autorec_entry_queue autorec_entries;
if(dae->dae_channel == NULL &&
dae->dae_channel_tag == NULL &&
- dae->dae_content_type.code == 0 &&
+ dae->dae_content_type == 0 &&
(dae->dae_title == NULL ||
dae->dae_title[0] == '\0') &&
dae->dae_brand == NULL &&
dae->dae_season == NULL &&
dae->dae_minduration == 0 &&
- dae->dae_maxduration == 0 &&
+ (dae->dae_maxduration == 0 || dae->dae_maxduration > 24 * 3600) &&
dae->dae_serieslink == NULL)
return 0; // Avoid super wildcard match
return 0;
}
- if(dae->dae_content_type.code != 0) {
- if (!epg_genre_list_contains(&e->episode->genre, &dae->dae_content_type, 1))
+ if(dae->dae_content_type != 0) {
+ epg_genre_t ct;
+ memset(&ct, 0, sizeof(ct));
+ ct.code = dae->dae_content_type;
+ if (!epg_genre_list_contains(&e->episode->genre, &ct, 1))
return 0;
}
- if(dae->dae_approx_time != 0) {
+ if(dae->dae_start >= 0) {
struct tm a_time;
struct tm ev_time;
localtime_r(&e->start, &a_time);
localtime_r(&e->start, &ev_time);
- a_time.tm_min = dae->dae_approx_time % 60;
- a_time.tm_hour = dae->dae_approx_time / 60;
+ a_time.tm_min = dae->dae_start % 60;
+ a_time.tm_hour = dae->dae_start / 60;
if(abs(mktime(&a_time) - mktime(&ev_time)) > 900)
return 0;
}
return 1;
}
-
/**
*
*/
dvr_autorec_entry_t *
-autorec_entry_find(const char *id, int create)
+dvr_autorec_create(const char *uuid, htsmsg_t *conf)
{
dvr_autorec_entry_t *dae;
- char buf[20];
- static int tally;
- if(id != NULL) {
- TAILQ_FOREACH(dae, &autorec_entries, dae_link)
- if(!strcmp(dae->dae_id, id))
- return dae;
- }
+ dae = calloc(1, sizeof(*dae));
- if(create == 0)
+ if (idnode_insert(&dae->dae_id, uuid, &dvr_autorec_entry_class, 0)) {
+ if (uuid)
+ tvhwarn("dvr", "invalid autorec entry uuid '%s'", uuid);
+ free(dae);
return NULL;
-
- dae = calloc(1, sizeof(dvr_autorec_entry_t));
- if(id == NULL) {
- tally++;
- snprintf(buf, sizeof(buf), "%d", tally);
- id = buf;
- } else {
- tally = MAX(atoi(id), tally);
}
+
dae->dae_weekdays = 0x7f;
dae->dae_pri = DVR_PRIO_NORMAL;
+ dae->dae_start = -1;
- dae->dae_id = strdup(id);
TAILQ_INSERT_TAIL(&autorec_entries, dae, dae_link);
+
+ idnode_load(&dae->dae_id, conf);
+
return dae;
}
-
+/**
+ *
+ */
+dvr_autorec_entry_t *
+dvr_autorec_add_series_link(const char *dvr_config_name,
+ epg_broadcast_t *event,
+ const char *creator, const char *comment)
+{
+ dvr_autorec_entry_t *dae;
+ htsmsg_t *conf;
+ char *title;
+ if (!event || !event->episode || !event->serieslink)
+ return NULL;
+ conf = htsmsg_create_map();
+ title = regexp_escape(epg_broadcast_get_title(event, NULL));
+ htsmsg_add_str(conf, "title", title);
+ free(title);
+ htsmsg_add_str(conf, "config_name", dvr_config_name ?: "");
+ htsmsg_add_str(conf, "channel", channel_get_name(event->channel));
+ htsmsg_add_str(conf, "serieslink", event->serieslink->uri);
+ htsmsg_add_str(conf, "creator", creator ?: "");
+ htsmsg_add_str(conf, "comment", comment ?: "");
+ dae = dvr_autorec_create(NULL, conf);
+ htsmsg_destroy(conf);
+ return dae;
+}
/**
*
*/
static void
-autorec_entry_destroy(dvr_autorec_entry_t *dae)
+autorec_entry_destroy(dvr_autorec_entry_t *dae, int delconf)
{
dvr_autorec_purge_spawns(dae);
- free(dae->dae_id);
+ if (delconf)
+ hts_settings_remove("dvr/autorec/%s", idnode_uuid_as_str(&dae->dae_id));
+ TAILQ_REMOVE(&autorec_entries, dae, dae_link);
+ idnode_unlink(&dae->dae_id);
+
+ free(dae->dae_name);
free(dae->dae_config_name);
free(dae->dae_creator);
free(dae->dae_comment);
dae->dae_season->putref(dae->dae_season);
if(dae->dae_serieslink)
dae->dae_serieslink->putref(dae->dae_serieslink);
-
- TAILQ_REMOVE(&autorec_entries, dae, dae_link);
free(dae);
}
/**
*
*/
-static void
-build_weekday_tags(htsmsg_t *l, int mask)
+void
+dvr_autorec_save(dvr_autorec_entry_t *dae)
{
- int i;
- for(i = 0; i < 7; i++) {
- if(mask & (1 << i))
- htsmsg_add_u32(l, NULL, i+1);
- }
-}
+ htsmsg_t *m = htsmsg_create_map();
-/**
- *
- */
-static int
-build_weekday_mask(htsmsg_t *l)
-{
- int r = 0;
- uint32_t u32;
- htsmsg_field_t *f;
- HTSMSG_FOREACH(f, l)
- if (!htsmsg_field_get_u32(f, &u32))
- r |= 1 << (u32 - 1);
- return r;
+ lock_assert(&global_lock);
+
+ idnode_save(&dae->dae_id, m);
+ hts_settings_save(m, "dvr/autorec/%s", idnode_uuid_as_str(&dae->dae_id));
+ htsmsg_destroy(m);
}
+/* **************************************************************************
+ * DVR Autorec Entry Class definition
+ * **************************************************************************/
-/**
- *
- */
-static htsmsg_t *
-autorec_record_build(dvr_autorec_entry_t *dae)
+static void
+dvr_autorec_entry_class_save(idnode_t *self)
{
- htsmsg_t *e = htsmsg_create_map();
- htsmsg_t *l = htsmsg_create_list();
-
- htsmsg_add_str(e, "id", dae->dae_id);
- htsmsg_add_u32(e, "enabled", !!dae->dae_enabled);
-
- if (dae->dae_config_name != NULL)
- htsmsg_add_str(e, "config_name", dae->dae_config_name);
- if(dae->dae_creator != NULL)
- htsmsg_add_str(e, "creator", dae->dae_creator);
- if(dae->dae_comment != NULL)
- htsmsg_add_str(e, "comment", dae->dae_comment);
-
- if(dae->dae_channel != NULL)
- htsmsg_add_str(e, "channel", channel_get_uuid(dae->dae_channel));
+ dvr_autorec_save((dvr_autorec_entry_t *)self);
+}
- if(dae->dae_channel_tag != NULL)
- htsmsg_add_str(e, "tag", dae->dae_channel_tag->ct_name);
+static void
+dvr_autorec_entry_class_delete(idnode_t *self)
+{
+ autorec_entry_destroy((dvr_autorec_entry_t *)self, 1);
+}
- htsmsg_add_u32(e, "contenttype",dae->dae_content_type.code);
+static const char *
+dvr_autorec_entry_class_get_title (idnode_t *self)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)self;
+ const char *s = "";
+ if (dae->dae_name && dae->dae_name[0] != '\0')
+ s = dae->dae_name;
+ else if (dae->dae_comment && dae->dae_comment[0] != '\0')
+ s = dae->dae_comment;
+ return s;
+}
- htsmsg_add_str(e, "title", dae->dae_title ?: "");
+static int
+dvr_autorec_entry_class_channel_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ channel_t *ch = v ? channel_find_by_uuid(v) : NULL;
+ if (ch == NULL) ch = v ? channel_find_by_name(v) : NULL;
+ if (ch == NULL) {
+ if (dae->dae_channel) {
+ LIST_REMOVE(dae, dae_channel_link);
+ dae->dae_channel = NULL;
+ return 1;
+ }
+ } else if (dae->dae_channel != ch) {
+ if (dae->dae_channel)
+ LIST_REMOVE(dae, dae_channel_link);
+ dae->dae_channel = ch;
+ LIST_INSERT_HEAD(&ch->ch_autorecs, dae, dae_channel_link);
+ return 1;
+ }
+ return 0;
+}
- htsmsg_add_u32(e, "approx_time", dae->dae_approx_time);
+static const void *
+dvr_autorec_entry_class_channel_get(void *o)
+{
+ static const char *ret;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ if (dae->dae_channel)
+ ret = idnode_uuid_as_str(&dae->dae_channel->ch_id);
+ else
+ ret = "";
+ return &ret;
+}
- build_weekday_tags(l, dae->dae_weekdays);
- htsmsg_add_msg(e, "weekdays", l);
+static htsmsg_t *
+dvr_autorec_entry_class_channel_list(void *o)
+{
+ 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");
+ return m;
+}
- if (dae->dae_minduration)
- htsmsg_add_u32(e, "minduration", dae->dae_minduration);
- if (dae->dae_maxduration)
- htsmsg_add_u32(e, "maxduration", dae->dae_maxduration);
+static int
+dvr_autorec_entry_class_title_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ const char *title = v ?: "";
+ if (strcmp(title, dae->dae_title ?: "")) {
+ if (dae->dae_title) {
+ regfree(&dae->dae_title_preg);
+ free(dae->dae_title);
+ dae->dae_title = NULL;
+ }
+ if (title != NULL && title[0] != '\0' &&
+ !regcomp(&dae->dae_title_preg, title,
+ REG_ICASE | REG_EXTENDED | REG_NOSUB))
+ dae->dae_title = strdup(title);
+ return 1;
+ }
+ return 0;
+}
- htsmsg_add_str(e, "pri", dvr_val2pri(dae->dae_pri));
-
- if (dae->dae_brand)
- htsmsg_add_str(e, "brand", dae->dae_brand->uri);
- if (dae->dae_season)
- htsmsg_add_str(e, "season", dae->dae_season->uri);
- if (dae->dae_serieslink)
- htsmsg_add_str(e, "serieslink", dae->dae_serieslink->uri);
+static int
+dvr_autorec_entry_class_tag_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ channel_tag_t *tag = v ? channel_tag_find_by_uuid(v) : NULL;
+ if (tag == NULL) tag = v ? channel_tag_find_by_name(v, 0) : NULL;
+ if (tag == NULL && dae->dae_channel_tag) {
+ LIST_REMOVE(dae, dae_channel_tag_link);
+ dae->dae_channel_tag = NULL;
+ return 1;
+ } else if (dae->dae_channel_tag != tag) {
+ if (dae->dae_channel_tag)
+ LIST_REMOVE(dae, dae_channel_tag_link);
+ dae->dae_channel_tag = tag;
+ LIST_INSERT_HEAD(&tag->ct_autorecs, dae, dae_channel_tag_link);
+ return 1;
+ }
+ return 0;
+}
- return e;
+static const void *
+dvr_autorec_entry_class_tag_get(void *o)
+{
+ static const char *ret;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ if (dae->dae_channel_tag)
+ ret = idnode_uuid_as_str(&dae->dae_channel_tag->ct_id);
+ else
+ ret = "";
+ return &ret;
}
-/**
- *
- */
static htsmsg_t *
-autorec_record_get_all(void *opaque)
+dvr_autorec_entry_class_tag_list(void *o)
{
- htsmsg_t *r = htsmsg_create_list();
- dvr_autorec_entry_t *dae;
-
- TAILQ_FOREACH(dae, &autorec_entries, dae_link)
- htsmsg_add_msg(r, NULL, autorec_record_build(dae));
+ 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_msg(m, "params", e);
+ return m;
+}
- return r;
+static int
+dvr_autorec_entry_class_time_set(void *o, const void *v, int *tm)
+{
+ const char *s = v;
+ int t;
+
+ if(s == NULL || s[0] == '\0')
+ t = -1;
+ else if(strchr(s, ':') != NULL)
+ // formatted time string - convert
+ t = (atoi(s) * 60) + atoi(s + 3);
+ else {
+ t = atoi(s);
+ }
+ if (t != *tm) {
+ *tm = t;
+ return 1;
+ }
+ return 0;
}
-/**
- *
- */
-static htsmsg_t *
-autorec_record_get(void *opaque, const char *id)
+static int
+dvr_autorec_entry_class_start_set(void *o, const void *v)
{
- dvr_autorec_entry_t *ae;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ return dvr_autorec_entry_class_time_set(o, v, &dae->dae_start);
+}
- if((ae = autorec_entry_find(id, 0)) == NULL)
- return NULL;
- return autorec_record_build(ae);
+#if 0
+static int
+dvr_autorec_entry_class_stop_set(void *o, const void *v)
+{
+ return dvr_autorec_entry_class_time_set(o, v, &dae->dae_stop);
}
+#endif
+static const void *
+dvr_autorec_entry_class_time_get(void *o, int tm)
+{
+ static const char *ret;
+ static char buf[16];
+ if (tm >= 0)
+ snprintf(buf, sizeof(buf), "%02d:%02d", tm / 60, tm % 60);
+ else
+ strcpy(buf, "Not set");
+ ret = buf;
+ return &ret;
+}
-/**
- *
- */
-static htsmsg_t *
-autorec_record_create(void *opaque)
+static const void *
+dvr_autorec_entry_class_start_get(void *o)
{
- return autorec_record_build(autorec_entry_find(NULL, 1));
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ return dvr_autorec_entry_class_time_get(o, dae->dae_start);
}
+#if 0
+static int
+dvr_autorec_entry_class_stop_get(void *o)
+{
+ return dvr_autorec_entry_class_time_get(o, v, &dae->dae_stop);
+}
+#endif
-/**
- *
- */
static htsmsg_t *
-autorec_record_update(void *opaque, const char *id, htsmsg_t *values,
- int maycreate)
+dvr_autorec_entry_class_time_list(void *o)
{
- int save;
- dvr_autorec_entry_t *dae;
- const char *s;
- channel_t *ch;
- channel_tag_t *ct;
- uint32_t u32;
- htsmsg_t *l;
-
- if((dae = autorec_entry_find(id, maycreate)) == NULL)
- return NULL;
-
- tvh_str_update(&dae->dae_config_name, htsmsg_get_str(values, "config_name"));
- tvh_str_update(&dae->dae_creator, htsmsg_get_str(values, "creator"));
- tvh_str_update(&dae->dae_comment, htsmsg_get_str(values, "comment"));
-
- if((s = htsmsg_get_str(values, "channel")) != NULL) {
- if(dae->dae_channel != NULL) {
- LIST_REMOVE(dae, dae_channel_link);
- dae->dae_channel = NULL;
- }
- ch = channel_find(s);
- if (!ch) ch = channel_find_by_name(s);
- if (ch) {
- LIST_INSERT_HEAD(&ch->ch_autorecs, dae, dae_channel_link);
- dae->dae_channel = ch;
- }
+ int i;
+ htsmsg_t *e, *l = htsmsg_create_list();
+ char buf[16];
+ e = htsmsg_create_map();
+ htsmsg_add_str(e, "key", "");
+ htsmsg_add_str(e, "val", "Not set");
+ htsmsg_add_msg(l, NULL, e);
+ for (i = 0; i < 24*60; i += 10) {
+ snprintf(buf, sizeof(buf), "%02d:%02d", i / 60, (i % 60));
+ e = htsmsg_create_map();
+ htsmsg_add_str(e, "key", buf);
+ htsmsg_add_str(e, "val", buf);
+ htsmsg_add_msg(l, NULL, e);
}
+ return l;
+}
- if((s = htsmsg_get_str(values, "title")) != NULL) {
- if(dae->dae_title != NULL) {
- free(dae->dae_title);
- dae->dae_title = NULL;
- regfree(&dae->dae_title_preg);
- }
+static htsmsg_t *
+dvr_autorec_entry_class_minduration_list(void *o)
+{
+ /* reuse */
+ return dvr_autorec_entry_class_time_list(o);
+}
- if(!regcomp(&dae->dae_title_preg, s,
- REG_ICASE | REG_EXTENDED | REG_NOSUB)) {
- dae->dae_title = strdup(s);
- }
+static int
+dvr_autorec_entry_class_config_name_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ dvr_config_t *cfg = v ? dvr_config_find_by_uuid(v) : NULL;
+ if (cfg == NULL) cfg = v ? dvr_config_find_by_name_default(v): NULL;
+ if (cfg == NULL && dae->dae_config_name) {
+ free(dae->dae_config_name);
+ return 1;
+ } else if (strcmp(dae->dae_config_name ?: "", cfg ? cfg->dvr_config_name : "")) {
+ free(dae->dae_config_name);
+ dae->dae_config_name = strdup(cfg->dvr_config_name);
+ return 1;
}
+ return 0;
+}
- if((s = htsmsg_get_str(values, "tag")) != NULL) {
- if(dae->dae_channel_tag != NULL) {
- LIST_REMOVE(dae, dae_channel_tag_link);
- dae->dae_channel_tag = NULL;
- }
- if((ct = channel_tag_find_by_name(s, 0)) != NULL) {
- LIST_INSERT_HEAD(&ct->ct_autorecs, dae, dae_channel_tag_link);
- dae->dae_channel_tag = ct;
- }
- }
+static int
+dvr_autorec_entry_class_weekdays_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ htsmsg_field_t *f;
+ uint32_t u32, bits = 0;
- if (!htsmsg_get_u32(values, "contenttype", &u32))
- dae->dae_content_type.code = u32;
-
- if((s = htsmsg_get_str(values, "approx_time")) != NULL) {
- if(strchr(s, ':') != NULL) {
- // formatted time string - convert
- dae->dae_approx_time = (atoi(s) * 60) + atoi(s + 3);
- } else if(strlen(s) == 0) {
- dae->dae_approx_time = 0;
- } else {
- dae->dae_approx_time = atoi(s);
- }
- }
+ HTSMSG_FOREACH(f, (htsmsg_t *)v)
+ if (!htsmsg_field_get_u32(f, &u32) && u32 > 0 && u32 < 8)
+ bits |= (1 << (u32 - 1));
- if(!htsmsg_get_u32(values, "minduration", &u32))
- dae->dae_minduration = u32;
+ if (bits != dae->dae_weekdays) {
+ dae->dae_weekdays = bits;
+ return 1;
+ }
+ return 0;
+}
- if(!htsmsg_get_u32(values, "maxduration", &u32))
- dae->dae_maxduration = u32;
+static const void *
+dvr_autorec_entry_class_weekdays_get(void *o)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ htsmsg_t *m = htsmsg_create_list();
+ int i;
+ for (i = 0; i < 7; i++)
+ if (dae->dae_weekdays & (1 << i))
+ htsmsg_add_u32(m, NULL, i + 1);
+ return m;
+}
- if((l = htsmsg_get_list(values, "weekdays")) != NULL)
- dae->dae_weekdays = build_weekday_mask(l);
+static const struct strtab dvr_autorec_entry_class_weekdays_tab[] = {
+ { "Mon", 1 },
+ { "Tue", 2 },
+ { "Wed", 3 },
+ { "Thu", 4 },
+ { "Fri", 5 },
+ { "Sat", 6 },
+ { "Sun", 7 },
+};
- if(!htsmsg_get_u32(values, "enabled", &u32))
- dae->dae_enabled = u32;
+static htsmsg_t *
+dvr_autorec_entry_class_weekdays_list ( void *o )
+{
+ return strtab2htsmsg(dvr_autorec_entry_class_weekdays_tab);
+}
- if((s = htsmsg_get_str(values, "pri")) != NULL)
- dae->dae_pri = dvr_pri2val(s);
+static char *
+dvr_autorec_entry_class_weekdays_rend(void *o)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ static char buf[32];
+ size_t l;
+ int i;
+ if (dae->dae_weekdays == 0x7f)
+ strcpy(buf + 1, "All days");
+ else if (dae->dae_weekdays == 0)
+ strcpy(buf + 1, "No days");
+ else {
+ buf[0] = '\0';
+ for (i = 0; i < 7; i++)
+ if (dae->dae_weekdays & (1 << i)) {
+ l = strlen(buf);
+ snprintf(buf + l, sizeof(buf) - l, ",%s",
+ val2str(i + 1, dvr_autorec_entry_class_weekdays_tab));
+ }
+ }
+ return buf + 1;
+}
- if((s = htsmsg_get_str(values, "brand")) != NULL) {
- dae->dae_brand = epg_brand_find_by_uri(s, 1, &save);
+static int
+dvr_autorec_entry_class_brand_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ int save;
+ epg_brand_t *brand = epg_brand_find_by_uri(v, 1, &save);
+ if (brand && dae->dae_brand != brand) {
if (dae->dae_brand)
- dae->dae_brand->getref((epg_object_t*)dae->dae_brand);
+ dae->dae_brand->putref((epg_object_t*)dae->dae_brand);
+ brand->getref((epg_object_t*)brand);
+ dae->dae_brand = brand;
+ return 1;
+ } else if (brand == NULL && dae->dae_brand) {
+ dae->dae_brand->putref((epg_object_t*)dae->dae_brand);
+ dae->dae_brand = NULL;
+ return 1;
}
- if((s = htsmsg_get_str(values, "season")) != NULL) {
- dae->dae_season = epg_season_find_by_uri(s, 1, &save);
+ return 0;
+}
+
+static const void *
+dvr_autorec_entry_class_brand_get(void *o)
+{
+ static const char *ret;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ if (dae->dae_brand)
+ ret = dae->dae_brand->uri;
+ else
+ ret = "";
+ return &ret;
+}
+
+static int
+dvr_autorec_entry_class_season_set(void *o, const void *v)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ int save;
+ epg_season_t *season = epg_season_find_by_uri(v, 1, &save);
+ if (season && dae->dae_season != season) {
if (dae->dae_season)
- dae->dae_season->getref((epg_object_t*)dae->dae_season);
- }
- if((s = htsmsg_get_str(values, "serieslink")) != NULL) {
- dae->dae_serieslink = epg_serieslink_find_by_uri(s, 1, &save);
- if (dae->dae_serieslink)
- dae->dae_serieslink->getref(dae->dae_serieslink);
+ dae->dae_season->putref((epg_object_t*)dae->dae_season);
+ season->getref((epg_object_t*)season);
+ dae->dae_season = season;
+ return 1;
+ } else if (season == NULL && dae->dae_season) {
+ dae->dae_season->putref((epg_object_t*)dae->dae_season);
+ dae->dae_season = NULL;
+ return 1;
}
- if (!dvr_autorec_in_init)
- dvr_autorec_changed(dae, 1);
-
- return autorec_record_build(dae);
+ return 0;
}
+static const void *
+dvr_autorec_entry_class_season_get(void *o)
+{
+ static const char *ret;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ if (dae->dae_season)
+ ret = dae->dae_season->uri;
+ else
+ ret = "";
+ return &ret;
+}
-/**
- *
- */
static int
-autorec_record_delete(void *opaque, const char *id)
+dvr_autorec_entry_class_series_link_set(void *o, const void *v)
{
- dvr_autorec_entry_t *dae;
-
- if((dae = autorec_entry_find(id, 0)) == NULL)
- return -1;
- autorec_entry_destroy(dae);
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ int save;
+ epg_serieslink_t *sl = epg_serieslink_find_by_uri(v, 1, &save);
+ if (sl && dae->dae_serieslink != sl) {
+ if (dae->dae_serieslink)
+ dae->dae_serieslink->putref((epg_object_t*)dae->dae_season);
+ sl->getref((epg_object_t*)sl);
+ dae->dae_serieslink = sl;
+ return 1;
+ } else if (sl == NULL && dae->dae_serieslink) {
+ dae->dae_season->putref((epg_object_t*)dae->dae_season);
+ dae->dae_season = NULL;
+ return 1;
+ }
return 0;
}
+static const void *
+dvr_autorec_entry_class_series_link_get(void *o)
+{
+ static const char *ret;
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ if (dae->dae_serieslink)
+ ret = dae->dae_serieslink->uri;
+ else
+ ret = "";
+ return &ret;
+}
-/**
- *
- */
-static const dtable_class_t autorec_dtc = {
- .dtc_record_get = autorec_record_get,
- .dtc_record_get_all = autorec_record_get_all,
- .dtc_record_create = autorec_record_create,
- .dtc_record_update = autorec_record_update,
- .dtc_record_delete = autorec_record_delete,
- .dtc_read_access = ACCESS_RECORDER,
- .dtc_write_access = ACCESS_RECORDER,
- .dtc_mutex = &global_lock,
+static htsmsg_t *
+dvr_autorec_entry_class_content_type_list(void *o)
+{
+ htsmsg_t *m = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "epg/content_type/list");
+ return m;
+}
+
+const idclass_t dvr_autorec_entry_class = {
+ .ic_class = "dvrautorec",
+ .ic_caption = "DVR Auto-Record Entry",
+ .ic_save = dvr_autorec_entry_class_save,
+ .ic_get_title = dvr_autorec_entry_class_get_title,
+ .ic_delete = dvr_autorec_entry_class_delete,
+ .ic_properties = (const property_t[]) {
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = "Enabled",
+ .off = offsetof(dvr_autorec_entry_t, dae_enabled),
+ },
+ {
+ .type = PT_STR,
+ .id = "name",
+ .name = "Name",
+ .off = offsetof(dvr_autorec_entry_t, dae_name),
+ },
+ {
+ .type = PT_STR,
+ .id = "title",
+ .name = "Title (Regexp)",
+ .set = dvr_autorec_entry_class_title_set,
+ .off = offsetof(dvr_autorec_entry_t, dae_title),
+ },
+ {
+ .type = PT_STR,
+ .id = "channel",
+ .name = "Channel",
+ .set = dvr_autorec_entry_class_channel_set,
+ .get = dvr_autorec_entry_class_channel_get,
+ .list = dvr_autorec_entry_class_channel_list,
+ },
+ {
+ .type = PT_STR,
+ .id = "tag",
+ .name = "Channel Tag",
+ .set = dvr_autorec_entry_class_tag_set,
+ .get = dvr_autorec_entry_class_tag_get,
+ .list = dvr_autorec_entry_class_tag_list,
+ },
+ {
+ .type = PT_STR,
+ .id = "start",
+ .name = "Starting Around",
+ .set = dvr_autorec_entry_class_start_set,
+ .get = dvr_autorec_entry_class_start_get,
+ .list = dvr_autorec_entry_class_time_list,
+ },
+ {
+ .type = PT_U32,
+ .islist = 1,
+ .id = "weekdays",
+ .name = "Week Days",
+ .set = dvr_autorec_entry_class_weekdays_set,
+ .get = dvr_autorec_entry_class_weekdays_get,
+ .list = dvr_autorec_entry_class_weekdays_list,
+ .rend = dvr_autorec_entry_class_weekdays_rend,
+ },
+ {
+ .type = PT_U32,
+ .id = "minduration",
+ .name = "Minimal Duration",
+ .list = dvr_autorec_entry_class_minduration_list,
+ .off = offsetof(dvr_autorec_entry_t, dae_minduration),
+ },
+ {
+ .type = PT_U32,
+ .id = "maxduration",
+ .name = "Maximal Duration",
+ .off = offsetof(dvr_autorec_entry_t, dae_maxduration),
+ },
+ {
+ .type = PT_U32,
+ .id = "content_type",
+ .name = "Content Type",
+ .list = dvr_autorec_entry_class_content_type_list,
+ .off = offsetof(dvr_autorec_entry_t, dae_content_type),
+ },
+ {
+ .type = PT_U32,
+ .id = "pri",
+ .name = "Priority",
+ .list = dvr_entry_class_pri_list,
+ .def.i = DVR_PRIO_NORMAL,
+ .off = offsetof(dvr_autorec_entry_t, dae_pri),
+ },
+ {
+ .type = PT_STR,
+ .id = "config_name",
+ .name = "DVR Configuration",
+ .set = dvr_autorec_entry_class_config_name_set,
+ .list = dvr_entry_class_config_name_list,
+ .off = offsetof(dvr_autorec_entry_t, dae_config_name),
+ },
+ {
+ .type = PT_STR,
+ .id = "brand",
+ .name = "Brand",
+ .set = dvr_autorec_entry_class_brand_set,
+ .get = dvr_autorec_entry_class_brand_get,
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "season",
+ .name = "Season",
+ .set = dvr_autorec_entry_class_season_set,
+ .get = dvr_autorec_entry_class_season_get,
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "serieslink",
+ .name = "Series Link",
+ .set = dvr_autorec_entry_class_series_link_set,
+ .get = dvr_autorec_entry_class_series_link_get,
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "creator",
+ .name = "Creator",
+ .off = offsetof(dvr_autorec_entry_t, dae_creator),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(dvr_autorec_entry_t, dae_comment),
+ },
+ {}
+ }
};
/**
void
dvr_autorec_init(void)
{
+ htsmsg_t *l, *c;
+ htsmsg_field_t *f;
+
TAILQ_INIT(&autorec_entries);
- autorec_dt = dtable_create(&autorec_dtc, "autorec", NULL);
dvr_autorec_in_init = 1;
- dtable_load(autorec_dt);
+ if((l = hts_settings_load("dvr/autorec")) != NULL) {
+ HTSMSG_FOREACH(f, l) {
+ if((c = htsmsg_get_map_by_field(f)) == NULL)
+ continue;
+ (void *)dvr_autorec_create(f->hmf_name, c);
+ }
+ }
dvr_autorec_in_init = 0;
}
dvr_autorec_entry_t *dae;
pthread_mutex_lock(&global_lock);
- while ((dae = TAILQ_FIRST(&autorec_entries)) != NULL) {
- TAILQ_REMOVE(&autorec_entries, dae, dae_link);
- free(dae);
- }
+ while ((dae = TAILQ_FIRST(&autorec_entries)) != NULL)
+ autorec_entry_destroy(dae, 0);
pthread_mutex_unlock(&global_lock);
- dtable_delete("autorec");
}
void
}
}
-static void
-_dvr_autorec_add(const char *config_name,
- const char *title, channel_t *ch,
- const char *tag, epg_genre_t *content_type,
- const int min_duration, const int max_duration,
- epg_brand_t *brand, epg_season_t *season,
- epg_serieslink_t *serieslink,
- int approx_time, epg_episode_num_t *epnum,
- const char *creator, const char *comment)
-{
- dvr_autorec_entry_t *dae;
- htsmsg_t *m;
- channel_tag_t *ct;
-
- if((dae = autorec_entry_find(NULL, 1)) == NULL)
- return;
-
- tvh_str_set(&dae->dae_config_name, config_name);
- tvh_str_set(&dae->dae_creator, creator);
- tvh_str_set(&dae->dae_comment, comment);
-
- if(ch) {
- LIST_INSERT_HEAD(&ch->ch_autorecs, dae, dae_channel_link);
- dae->dae_channel = ch;
- }
-
- if(title != NULL &&
- !regcomp(&dae->dae_title_preg, title,
- REG_ICASE | REG_EXTENDED | REG_NOSUB)) {
- dae->dae_title = strdup(title);
- }
-
- if(tag != NULL && (ct = channel_tag_find_by_uuid(tag)) != NULL) {
- LIST_INSERT_HEAD(&ct->ct_autorecs, dae, dae_channel_tag_link);
- dae->dae_channel_tag = ct;
- }
-
- dae->dae_enabled = 1;
- if (content_type)
- dae->dae_content_type.code = content_type->code;
-
- if (min_duration)
- dae->dae_minduration = min_duration;
-
- if (max_duration)
- dae->dae_maxduration = max_duration;
-
- if(serieslink) {
- serieslink->getref(serieslink);
- dae->dae_serieslink = serieslink;
- }
-
- dae->dae_approx_time = approx_time;
-
- m = autorec_record_build(dae);
- hts_settings_save(m, "%s/%s", "autorec", dae->dae_id);
- htsmsg_destroy(m);
-
- /* Notify web clients that we have messed with the tables */
-
- notify_reload("autorec");
-
- dvr_autorec_changed(dae, 1);
-}
-
-void
-dvr_autorec_add(const char *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)
-{
- channel_t *ch = NULL;
- if(channel != NULL) ch = channel_find(channel);
- _dvr_autorec_add(config_name, title, ch, tag, content_type,
- min_duration, max_duration,
- NULL, NULL, NULL, 0, NULL, creator, comment);
-}
-
-void dvr_autorec_add_series_link
- ( const char *dvr_config_name, epg_broadcast_t *event,
- const char *creator, const char *comment )
-{
- char *title;
- if (!event || !event->episode) return;
- title = regexp_escape(epg_broadcast_get_title(event, NULL));
- _dvr_autorec_add(dvr_config_name,
- title,
- event->channel,
- NULL, 0, // tag/content type
- 0,INT_MAX,
- NULL,
- NULL,
- event->serieslink,
- 0, NULL,
- creator, comment);
- if (title)
- free(title);
-}
-
-
/**
*
*/
dvr_autorec_entry_t *dae;
htsmsg_t *m;
- while((dae = LIST_FIRST(&ch->ch_autorecs)) != NULL) {
- if (delconf)
- dtable_record_erase(autorec_dt, dae->dae_id);
- autorec_entry_destroy(dae);
- }
+ while((dae = LIST_FIRST(&ch->ch_autorecs)) != NULL)
+ autorec_entry_destroy(dae, delconf);
/* Notify web clients that we have messed with the tables */
m = htsmsg_create_map();
#include "streaming.h"
#include "intlconv.h"
#include "dbus.h"
-
-static int de_tally;
+#include "imagecache.h"
+#include "access.h"
int dvr_iov_max;
static gtimer_t dvr_dbus_timer;
#endif
+static void dvr_entry_remove(dvr_entry_t *de, int delconf);
static void dvr_timer_expire(void *aux);
static void dvr_timer_start_recording(void *aux);
static void dvr_timer_stop_recording(void *aux);
+static int dvr_entry_class_disp_title_set(void *o, const void *v);
+
+/*
+ * Start / stop time calculators
+ */
+static int
+dvr_entry_get_start_time( dvr_entry_t *de )
+{
+ time_t extra = de->de_start_extra;
+
+ if (extra == 0) {
+ if (de->de_channel)
+ extra = de->de_channel->ch_dvr_extra_time_pre;
+ else
+ extra = de->de_config->dvr_extra_time_pre;
+ }
+ /* Note 30 seconds might not be enough (rotors) */
+ return de->de_start - (60 * extra) - 30;
+}
+
+static int
+dvr_entry_get_stop_time( dvr_entry_t *de )
+{
+ time_t extra = de->de_stop_extra;
+
+ if (extra == 0) {
+ if (de->de_channel)
+ extra = de->de_channel->ch_dvr_extra_time_post;
+ else
+ extra = de->de_config->dvr_extra_time_pre;
+ }
+ return de->de_stop + (60 * extra);
+}
+
+int
+dvr_entry_get_mc( dvr_entry_t *de )
+{
+ if (de->de_mc >= 0)
+ return de->de_mc;
+ return de->de_config->dvr_mc;
+}
/*
* DBUS next dvr start notifications
dvr_dbus_timer_cb( void *aux )
{
dvr_entry_t *de;
- time_t result, preamble, max = 0;
+ time_t result, start, max = 0;
static time_t last_result = 0;
lock_assert(&global_lock);
LIST_FOREACH(de, &dvrentries, de_global_link) {
if (de->de_sched_state != DVR_SCHEDULED)
continue;
- preamble = de->de_start - (60 * de->de_start_extra) - 30;
- if (dispatch_clock < preamble && preamble > max)
- max = preamble;
+ start = dvr_entry_get_start_time(de);
+ if (dispatch_clock < start && start > max)
+ max = start;
}
/* lower the maximum value */
result = max;
LIST_FOREACH(de, &dvrentries, de_global_link) {
if (de->de_sched_state != DVR_SCHEDULED)
continue;
- preamble = de->de_start - (60 * de->de_start_extra) - 30;
- if (dispatch_clock < preamble && preamble < result)
- result = preamble;
+ start = dvr_entry_get_start_time(de);
+ if (dispatch_clock < start && start < result)
+ result = start;
}
/* different? send it.... */
if (result && result != last_result) {
}
}
-
-/**
- *
- */
-static void
-dvrdb_changed(void)
-{
- htsmsg_t *m = htsmsg_create_map();
- htsmsg_add_u32(m, "reload", 1);
- notify_by_msg("dvrdb", m);
-}
-
-/**
- *
- */
-static void
-dvrconfig_changed(void)
-{
- htsmsg_t *m = htsmsg_create_map();
- htsmsg_add_u32(m, "reload", 1);
- notify_by_msg("dvrconfig", m);
-}
-
-
/**
*
*/
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "updateEntry", 1);
- htsmsg_add_u32(m, "id", de->de_id);
+ htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&de->de_id));
htsmsg_add_str(m, "status", dvr_entry_status(de));
htsmsg_add_str(m, "schedstate", dvr_entry_schedstatus(de));
notify_by_msg("dvrdb", m);
/**
*
*/
-static void
+static int
dvr_charset_update(dvr_config_t *cfg, const char *charset)
{
const char *s, *id;
+ int change = strcmp(cfg->dvr_charset ?: "", charset ?: "");
free(cfg->dvr_charset);
free(cfg->dvr_charset_id);
id = intlconv_charset_id(s, 1, 1);
cfg->dvr_charset = s ? strdup(s) : NULL;
cfg->dvr_charset_id = id ? strdup(id) : NULL;
+ return change;
}
/**
{
struct tm tm;
char buf[40];
- dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
+ dvr_config_t *cfg = de->de_config;
if(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE)
snprintf(output, outlen, "%s-", DVR_CH_NAME(de));
static void
dvr_entry_set_timer(dvr_entry_t *de)
{
- time_t now, preamble;
- dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
+ time_t now, start, stop;
+ dvr_config_t *cfg = de->de_config;
time(&now);
- preamble = de->de_start - (60 * de->de_start_extra) - 30;
+ start = dvr_entry_get_start_time(de);
+ stop = dvr_entry_get_stop_time(de);
+
+ if(now >= stop || de->de_dont_reschedule) {
- if(now >= de->de_stop || de->de_dont_reschedule) {
if(de->de_filename == NULL)
de->de_sched_state = DVR_MISSED_TIME;
else
_dvr_entry_completed(de);
gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de,
- de->de_stop + cfg->dvr_retention_days * 86400);
+ de->de_stop + cfg->dvr_retention_days * 86400);
} else if (de->de_sched_state == DVR_RECORDING) {
- gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de,
- de->de_stop + (60 * de->de_stop_extra));
+
+ gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de, stop);
} else if (de->de_channel) {
+
de->de_sched_state = DVR_SCHEDULED;
- tvhtrace("dvr", "entry timer scheduled for %"PRItime_t, preamble);
- gtimer_arm_abs(&de->de_timer, dvr_timer_start_recording, de, preamble);
+ tvhtrace("dvr", "entry timer scheduled for %"PRItime_t, start);
+ gtimer_arm_abs(&de->de_timer, dvr_timer_start_recording, de, start);
#if ENABLE_DBUS_1
gtimer_arm(&dvr_dbus_timer, dvr_dbus_timer_cb, NULL, 5);
#endif
+
} else {
+
de->de_sched_state = DVR_NOSTATE;
+
}
}
-/**
- *
- */
-static void
-dvr_entry_link(dvr_entry_t *de)
-{
- de->de_refcnt = 1;
-
- LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
-
- dvr_entry_set_timer(de);
-
- htsp_dvr_entry_add(de);
-}
/**
* Find dvr entry using 'fuzzy' search
}
/**
- * Create the event
+ * Create the event from config
*/
-static dvr_entry_t *_dvr_entry_create (
- const char *config_name, epg_broadcast_t *e,
- 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 *de;
- char tbuf[64];
- struct tm tm;
- time_t t;
- dvr_config_t *cfg = dvr_config_find_by_name_default(config_name);
+ dvr_entry_t *de, *de2;
+ int64_t start, stop;
+ const char *s;
- LIST_FOREACH(de, &ch->ch_dvrs, de_channel_link)
- if(de->de_start == start && de->de_sched_state != DVR_COMPLETED)
+ if (conf) {
+ if (htsmsg_get_s64(conf, "start", &start))
return NULL;
+ if (htsmsg_get_s64(conf, "stop", &stop))
+ return NULL;
+ if ((htsmsg_get_str(conf, "channel")) == NULL &&
+ (htsmsg_get_str(conf, "channelname")) == NULL)
+ return NULL;
+ }
+
+ de = calloc(1, sizeof(*de));
- de = calloc(1, sizeof(dvr_entry_t));
- de->de_id = ++de_tally;
+ if (idnode_insert(&de->de_id, uuid, &dvr_entry_class, IDNODE_SHORT_UUID)) {
+ if (uuid)
+ tvhwarn("dvr", "invalid entry uuid '%s'", uuid);
+ free(de);
+ return NULL;
+ }
- ch = de->de_channel = ch;
- LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
+ de->de_mc = -1;
+ idnode_load(&de->de_id, conf);
- de->de_mc = cfg->dvr_mc;
+ /* special case, becaous PO_NOSAVE, load ignores it */
+ if (de->de_title == NULL &&
+ (s = htsmsg_get_str(conf, "disp_title")) != NULL)
+ dvr_entry_class_disp_title_set(de, s);
- de->de_start = start;
- de->de_stop = stop;
- de->de_pri = pri;
- if (start_extra)
- de->de_start_extra = start_extra;
- else if (ch->ch_dvr_extra_time_pre)
- de->de_start_extra = ch->ch_dvr_extra_time_pre;
- else
- de->de_start_extra = cfg->dvr_extra_time_pre;
- if (stop_extra)
- de->de_stop_extra = stop_extra;
- else if (ch->ch_dvr_extra_time_post)
- de->de_stop_extra = ch->ch_dvr_extra_time_post;
- else
- de->de_stop_extra = cfg->dvr_extra_time_post;
- de->de_config_name = strdup(cfg->dvr_config_name);
- de->de_creator = strdup(creator);
+ de->de_refcnt = 1;
+
+ LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
+
+ if (de->de_channel) {
+ LIST_FOREACH(de2, &de->de_channel->ch_dvrs, de_channel_link)
+ if(de2 != de &&
+ de2->de_start == de->de_start &&
+ de2->de_sched_state != DVR_COMPLETED) {
+ dvr_entry_remove(de, 0);
+ return NULL;
+ }
+ }
+
+
+ dvr_entry_set_timer(de);
+ htsp_dvr_entry_add(de);
- de->de_desc = NULL;
- // TODO: this really needs updating
+ return de;
+}
+
+/**
+ * Create the event
+ */
+static dvr_entry_t *
+_dvr_entry_create(const char *config_uuid, epg_broadcast_t *e,
+ 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 *de;
+ char tbuf[64];
+ struct tm tm;
+ time_t t;
+ lang_str_t *l;
+ htsmsg_t *conf;
+
+ conf = htsmsg_create_map();
+ htsmsg_add_s64(conf, "start", start);
+ htsmsg_add_s64(conf, "stop", stop);
+ htsmsg_add_str(conf, "channel", idnode_uuid_as_str(&ch->ch_id));
+ htsmsg_add_u32(conf, "pri", pri);
+ htsmsg_add_str(conf, "config_name", config_uuid ?: "");
+ htsmsg_add_s64(conf, "start_extra", start_extra);
+ htsmsg_add_s64(conf, "stop_extra", stop_extra);
+ htsmsg_add_str(conf, "creator", creator ?: "");
if (e) {
- de->de_dvb_eid = e->dvb_eid;
+ htsmsg_add_u32(conf, "dvb_eid", e->dvb_eid);
if (e->episode && e->episode->title)
- de->de_title = lang_str_copy(e->episode->title);
+ lang_str_serialize(e->episode->title, conf, "title");
if (e->description)
- de->de_desc = lang_str_copy(e->description);
+ lang_str_serialize(e->description, conf, "description");
else if (e->episode && e->episode->description)
- de->de_desc = lang_str_copy(e->episode->description);
+ lang_str_serialize(e->episode->description, conf, "description");
else if (e->summary)
- de->de_desc = lang_str_copy(e->summary);
+ lang_str_serialize(e->summary, conf, "description");
else if (e->episode && e->episode->summary)
- de->de_desc = lang_str_copy(e->episode->summary);
+ lang_str_serialize(e->episode->summary, conf, "description");
} else if (title) {
- de->de_title = lang_str_create();
- lang_str_add(de->de_title, title, lang, 0);
+ l = lang_str_create();
+ lang_str_add(l, title, lang, 0);
+ lang_str_serialize(l, conf, "title");
if (description) {
- de->de_desc = lang_str_create();
- lang_str_add(de->de_desc, description, lang, 0);
+ l = lang_str_create();
+ lang_str_add(l, description, lang, 0);
+ lang_str_serialize(l, conf, "description");
}
}
- if (content_type) de->de_content_type = *content_type;
- de->de_bcast = e;
- if (e) e->getref((epg_object_t*)e);
+ if (content_type)
+ htsmsg_add_u32(conf, "content_type", content_type->code / 16);
+ if (e)
+ htsmsg_add_u32(conf, "broadcast", e->id);
+ if (dae)
+ htsmsg_add_str(conf, "autorec", idnode_uuid_as_str(&dae->dae_id));
+
+ de = dvr_entry_create(NULL, conf);
- dvr_entry_link(de);
+ htsmsg_destroy(conf);
+
+ if (de == NULL)
+ return NULL;
- t = de->de_start - de->de_start_extra * 60;
+ t = dvr_entry_get_start_time(de);
localtime_r(&t, &tm);
if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0)
*tbuf = 0;
- if(dae != NULL) {
- de->de_autorec = dae;
- LIST_INSERT_HEAD(&dae->dae_spawns, de, de_autorec_link);
- }
-
- tvhlog(LOG_INFO, "dvr", "entry %d \"%s\" on \"%s\" starting at %s, "
+ tvhlog(LOG_INFO, "dvr", "entry %s \"%s\" on \"%s\" starting at %s, "
"scheduled for recording by \"%s\"",
- de->de_id,
+ idnode_uuid_as_str(&de->de_id),
lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), tbuf, creator);
-
- dvrdb_changed();
+
dvr_entry_save(de);
return de;
}
-
/**
*
*/
dvr_entry_t *
-dvr_entry_create(const char *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)
-{
- return _dvr_entry_create(config_name, NULL,
+dvr_entry_create_htsp(const char *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)
+{
+ return _dvr_entry_create(config_uuid, NULL,
ch, start, stop, start_extra, stop_extra,
title, description, lang, content_type,
creator, dae, pri);
*
*/
dvr_entry_t *
-dvr_entry_create_by_event(const char *config_name,
+dvr_entry_create_by_event(const char *config_uuid,
epg_broadcast_t *e,
time_t start_extra, time_t stop_extra,
const char *creator,
if(!e->channel || !e->episode || !e->episode->title)
return NULL;
- return _dvr_entry_create(config_name, e,
+ return _dvr_entry_create(config_uuid, e,
e->channel, e->start, e->stop,
start_extra, stop_extra,
NULL, NULL, NULL,
if (de->de_bcast->episode == e->episode) return 1;
if (has_epnum) {
- dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
- int ep_dup_det = (cfg->dvr_flags & DVR_EPISODE_DUPLICATE_DETECTION);
+ int ep_dup_det = (de->de_config->dvr_flags & DVR_EPISODE_DUPLICATE_DETECTION);
if (ep_dup_det) {
const char* de_title = lang_str_get(de->de_bcast->episode->title, NULL);
dvr_entry_create_by_event(dae->dae_config_name, e, 0, 0, buf, dae, dae->dae_pri);
}
-
/**
*
*/
return;
}
+ idnode_unlink(&de->de_id);
+
if(de->de_autorec != NULL)
LIST_REMOVE(de, de_autorec_link);
free(de->de_creator);
if (de->de_title) lang_str_destroy(de->de_title);
if (de->de_desc) lang_str_destroy(de->de_desc);
- if(de->de_bcast) de->de_bcast->putref((epg_object_t*)de->de_bcast);
+ if (de->de_bcast) de->de_bcast->putref((epg_object_t*)de->de_bcast);
free(de);
}
-
-
-
/**
*
*/
dvr_entry_remove(dvr_entry_t *de, int delconf)
{
if (delconf)
- hts_settings_remove("dvr/log/%d", de->de_id);
+ hts_settings_remove("dvr/log/%s", idnode_uuid_as_str(&de->de_id));
htsp_dvr_entry_delete(de);
free(de->de_channel_name);
de->de_channel_name = NULL;
- dvrdb_changed();
-
dvr_entry_dec_ref(de);
}
-
-/**
- *
- */
-static void
-dvr_db_load_one(htsmsg_t *c, int id)
-{
- dvr_entry_t *de;
- const char *chuuid, *chname, *s, *creator;
- channel_t *ch;
- uint32_t start, stop, bcid, u32;
- int d;
- dvr_config_t *cfg;
- lang_str_t *title, *ls;
-
- if(htsmsg_get_u32(c, "start", &start))
- return;
- if(htsmsg_get_u32(c, "stop", &stop))
- return;
-
- chname = htsmsg_get_str(c, "channelname");
- chuuid = htsmsg_get_str(c, "channel");
- ch = chuuid ? channel_find(chuuid) : NULL;
-
- /* Backwards compat */
- if (!ch && !chname) {
- chname = chuuid;
- ch = channel_find_by_name(chname);
- }
-
- s = htsmsg_get_str(c, "config_name");
- cfg = dvr_config_find_by_name_default(s);
-
- if(!(title = lang_str_deserialize(c, "title")))
- return;
-
- if((creator = htsmsg_get_str(c, "creator")) == NULL)
- return;
-
- de = calloc(1, sizeof(dvr_entry_t));
- de->de_id = id;
-
- de_tally = MAX(id, de_tally);
-
- if (ch) {
- de->de_channel = ch;
- LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
- } else {
- de->de_channel_name = strdup(chname);
- }
-
- de->de_start = start;
- de->de_stop = stop;
- de->de_config_name = strdup(cfg->dvr_config_name);
- de->de_creator = strdup(creator);
- de->de_title = title;
- de->de_pri = dvr_pri2val(htsmsg_get_str(c, "pri"));
- if (!htsmsg_get_u32(c, "dvb_eid", &u32))
- de->de_dvb_eid = (uint16_t)u32;
-
- if(htsmsg_get_s32(c, "start_extra", &d))
- if (ch && ch->ch_dvr_extra_time_pre)
- de->de_start_extra = ch->ch_dvr_extra_time_pre;
- else
- de->de_start_extra = cfg->dvr_extra_time_pre;
- else
- de->de_start_extra = d;
-
- if(htsmsg_get_s32(c, "stop_extra", &d))
- if (ch && ch->ch_dvr_extra_time_post)
- de->de_stop_extra = ch->ch_dvr_extra_time_post;
- else
- de->de_stop_extra = cfg->dvr_extra_time_post;
- else
- de->de_stop_extra = d;
-
-
- if ((ls = lang_str_deserialize(c, "description")))
- de->de_desc = ls;
- tvh_str_set(&de->de_filename, htsmsg_get_str(c, "filename"));
-
- htsmsg_get_u32(c, "errorcode", &de->de_last_error);
- htsmsg_get_u32(c, "errors", &de->de_errors);
-
- htsmsg_get_u32(c, "noresched", &de->de_dont_reschedule);
-
- s = htsmsg_get_str(c, "autorec");
- if(s != NULL) {
- dvr_autorec_entry_t *dae = autorec_entry_find(s, 0);
-
- if(dae != NULL) {
- de->de_autorec = dae;
- LIST_INSERT_HEAD(&dae->dae_spawns, de, de_autorec_link);
- }
- }
-
-
- de->de_content_type.code = htsmsg_get_u32_or_default(c, "contenttype", 0);
-
- if (!htsmsg_get_u32(c, "broadcast", &bcid)) {
- de->de_bcast = epg_broadcast_find_by_id(bcid, ch);
- if (de->de_bcast) {
- de->de_bcast->getref((epg_object_t*)de->de_bcast);
- }
- }
-
- de->de_mc = htsmsg_get_u32_or_default(c, "container", MC_MATROSKA);
-
- dvr_entry_link(de);
-}
-
-
/**
*
*/
HTSMSG_FOREACH(f, l) {
if((c = htsmsg_get_map_by_field(f)) == NULL)
continue;
- dvr_db_load_one(c, atoi(f->hmf_name));
+ (void)dvr_entry_create(f->hmf_name, c);
}
htsmsg_destroy(l);
}
}
-
-
/**
*
*/
lock_assert(&global_lock);
- if (de->de_channel)
- htsmsg_add_str(m, "channel", channel_get_uuid(de->de_channel));
- htsmsg_add_str(m, "channelname", DVR_CH_NAME(de));
- htsmsg_add_u32(m, "start", de->de_start);
- htsmsg_add_u32(m, "stop", de->de_stop);
-
- htsmsg_add_s32(m, "start_extra", de->de_start_extra);
- htsmsg_add_s32(m, "stop_extra", de->de_stop_extra);
-
- htsmsg_add_str(m, "config_name", de->de_config_name);
-
- htsmsg_add_str(m, "creator", de->de_creator);
-
- if(de->de_filename != NULL)
- htsmsg_add_str(m, "filename", de->de_filename);
-
- lang_str_serialize(de->de_title, m, "title");
-
- if(de->de_dvb_eid)
- htsmsg_add_u32(m, "dvb_eid", de->de_dvb_eid);
-
- if(de->de_desc != NULL)
- lang_str_serialize(de->de_desc, m, "description");
-
- htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri));
-
- if(de->de_last_error)
- htsmsg_add_u32(m, "errorcode", de->de_last_error);
-
- if(de->de_errors)
- htsmsg_add_u32(m, "errors", de->de_errors);
-
- htsmsg_add_u32(m, "noresched", de->de_dont_reschedule);
-
- if(de->de_autorec != NULL)
- htsmsg_add_str(m, "autorec", de->de_autorec->dae_id);
-
- if(de->de_content_type.code)
- htsmsg_add_u32(m, "contenttype", de->de_content_type.code);
-
- if(de->de_bcast)
- htsmsg_add_u32(m, "broadcast", de->de_bcast->id);
-
- htsmsg_add_u32(m, "container", de->de_mc);
-
- hts_settings_save(m, "dvr/log/%d", de->de_id);
+ idnode_save(&de->de_id, m);
+ hts_settings_save(m, "dvr/log/%s", idnode_uuid_as_str(&de->de_id));
htsmsg_destroy(m);
}
/* Genre */
if (e && e->episode) {
epg_genre_t *g = LIST_FIRST(&e->episode->genre);
- if (g && (g->code != de->de_content_type.code)) {
- de->de_content_type.code = g->code;
+ if (g && (g->code / 16) != de->de_content_type) {
+ de->de_content_type = g->code / 16;
save = 1;
}
}
/* Existing entry */
if ((de = dvr_entry_find_by_event(e))) {
tvhtrace("dvr",
- "dvr entry %d event replaced %s on %s @ %"PRItime_t
+ "dvr entry %s event replaced %s on %s @ %"PRItime_t
" to %"PRItime_t,
- de->de_id, epg_broadcast_get_title(e, NULL),
+ idnode_uuid_as_str(&de->de_id),
+ epg_broadcast_get_title(e, NULL),
channel_get_name(e->channel),
e->start, e->stop);
if (de->de_channel != e->channel) continue;
if (dvr_entry_fuzzy_match(de, e)) {
tvhtrace("dvr",
- "dvr entry %d link to event %s on %s @ %"PRItime_t
+ "dvr entry %s link to event %s on %s @ %"PRItime_t
" to %"PRItime_t,
- de->de_id, epg_broadcast_get_title(e, NULL),
+ idnode_uuid_as_str(&de->de_id),
+ epg_broadcast_get_title(e, NULL),
channel_get_name(e->channel),
e->start, e->stop);
e->getref(e);
static void
dvr_stop_recording(dvr_entry_t *de, int stopcode, int delconf)
{
- dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
+ dvr_config_t *cfg = de->de_config;
if (de->de_rec_state == DVR_RS_PENDING || de->de_rec_state == DVR_RS_WAIT_PROGRAM_START)
de->de_sched_state = DVR_MISSED_TIME;
{
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link)
- if(de->de_id == id)
+ if(idnode_get_short_uuid(&de->de_id) == id)
break;
return de;
}
dvr_stop_recording(de, SM_CODE_SOURCE_DELETED, delconf);
}
-/**
- *
- */
-void
-dvr_destroy_by_channel(channel_t *ch, int delconf)
+
+/* **************************************************************************
+ * DVR Entry Class definition
+ * **************************************************************************/
+
+static void
+dvr_entry_class_save(idnode_t *self)
{
- dvr_entry_t *de;
+ dvr_entry_save((dvr_entry_t *)self);
+}
- while((de = LIST_FIRST(&ch->ch_dvrs)) != NULL) {
- LIST_REMOVE(de, de_channel_link);
- de->de_channel = NULL;
- de->de_channel_name = strdup(channel_get_name(ch));
- dvr_entry_purge(de, delconf);
- }
+static void
+dvr_entry_class_delete(idnode_t *self)
+{
+ dvr_entry_remove((dvr_entry_t *)self, 1);
}
-/**
- *
- */
-void
-dvr_init(void)
+static const char *
+dvr_entry_class_get_title (idnode_t *self)
{
- htsmsg_t *m, *l;
- htsmsg_field_t *f;
+ dvr_entry_t *de = (dvr_entry_t *)self;
const char *s;
- char buf[500];
- const char *homedir;
- struct stat st;
- uint32_t u32;
- dvr_config_t *cfg;
-
- dvr_iov_max = sysconf(_SC_IOV_MAX);
-
- /* Default settings */
+ s = lang_str_get(de->de_title, NULL);
+ if (s == NULL || s[0] == '\0')
+ s = lang_str_get(de->de_desc, NULL);
+ return s;
+}
- LIST_INIT(&dvrconfigs);
- cfg = dvr_config_create("");
+static int
+dvr_entry_class_config_name_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *s = v ?: "";
+ if (strcmp(s, de->de_config_name ?: "")) {
+ free(de->de_config_name);
+ de->de_config_name = strdup(v);
+ de->de_config = dvr_config_find_by_name_default(de->de_config_name);
+ return 1;
+ }
+ /* for sure */
+ de->de_config = dvr_config_find_by_name_default(de->de_config_name);
+ return 0;
+}
- /* Override settings with config */
+static int
+dvr_entry_class_channel_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ channel_t *ch = v ? channel_find_by_uuid(v) : NULL;
+ if (!de->de_config_name)
+ dvr_entry_class_config_name_set(o, "");
+ if (ch == NULL) {
+ if (de->de_channel) {
+ LIST_REMOVE(de, de_channel_link);
+ de->de_channel = NULL;
+ return 1;
+ }
+ } else if (de->de_channel != ch) {
+ if (de->de_channel)
+ LIST_REMOVE(de, de_channel_link);
+ free(de->de_channel_name);
+ de->de_channel_name = strdup(channel_get_name(ch));
+ de->de_channel = ch;
+ LIST_INSERT_HEAD(&ch->ch_dvrs, de, de_channel_link);
+ return 1;
+ }
+ return 0;
+}
- l = hts_settings_load("dvr");
- if(l != NULL) {
- HTSMSG_FOREACH(f, l) {
- m = htsmsg_get_map_by_field(f);
- if(m == NULL)
- continue;
-
- s = htsmsg_get_str(m, "config_name");
- cfg = dvr_config_find_by_name(s);
- if(cfg == NULL)
- cfg = dvr_config_create(s);
-
- cfg->dvr_mc = htsmsg_get_u32_or_default(m, "container", MC_MATROSKA);
- cfg->dvr_muxcnf.m_cache
- = htsmsg_get_u32_or_default(m, "cache", MC_CACHE_DONTKEEP);
-
- if(!htsmsg_get_u32(m, "rewrite-pat", &u32)) {
- if (u32)
- cfg->dvr_muxcnf.m_flags |= MC_REWRITE_PAT;
- else
- cfg->dvr_muxcnf.m_flags &= ~MC_REWRITE_PAT;
- }
- if(!htsmsg_get_u32(m, "rewrite-pmt", &u32)) {
- if (u32)
- cfg->dvr_muxcnf.m_flags |= MC_REWRITE_PMT;
- else
- cfg->dvr_muxcnf.m_flags &= ~MC_REWRITE_PMT;
- }
+static const void *
+dvr_entry_class_channel_get(void *o)
+{
+ static const char *ret;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_channel)
+ ret = idnode_uuid_as_str(&de->de_channel->ch_id);
+ else
+ ret = "";
+ return &ret;
+}
- htsmsg_get_s32(m, "pre-extra-time", &cfg->dvr_extra_time_pre);
- htsmsg_get_s32(m, "post-extra-time", &cfg->dvr_extra_time_post);
- htsmsg_get_u32(m, "retention-days", &cfg->dvr_retention_days);
- tvh_str_set(&cfg->dvr_storage, htsmsg_get_str(m, "storage"));
+static htsmsg_t *
+dvr_entry_class_channel_list(void *o)
+{
+ 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");
+ return m;
+}
-/*
- * 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
- */
+static int
+dvr_entry_class_channel_name_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ channel_t *ch;
+ if (!de->de_config_name)
+ dvr_entry_class_config_name_set(o, "");
+ if (!strcmp(de->de_channel_name ?: "", v ?: ""))
+ return 0;
+ ch = v ? channel_find_by_name(v) : NULL;
+ if (ch) {
+ return dvr_entry_class_channel_set(o, idnode_uuid_as_str(&ch->ch_id));
+ } else {
+ free(de->de_channel_name);
+ de->de_channel_name = strdup(v);
+ return 1;
+ }
+}
- if ((s = htsmsg_get_str(m, "file-permissions")))
- cfg->dvr_muxcnf.m_file_permissions = (int)strtol(s,NULL,0);
-
- if ((s = htsmsg_get_str(m, "directory-permissions")))
- cfg->dvr_muxcnf.m_directory_permissions = (int)strtol(s,NULL,0);
-
- if(!htsmsg_get_u32(m, "day-dir", &u32) && u32)
- cfg->dvr_flags |= DVR_DIR_PER_DAY;
-
- if(!htsmsg_get_u32(m, "channel-dir", &u32) && u32)
- cfg->dvr_flags |= DVR_DIR_PER_CHANNEL;
-
- if(!htsmsg_get_u32(m, "channel-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_CHANNEL_IN_TITLE;
-
- if(!htsmsg_get_u32(m, "date-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_DATE_IN_TITLE;
-
- if(!htsmsg_get_u32(m, "time-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_TIME_IN_TITLE;
-
- if(!htsmsg_get_u32(m, "whitespace-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_WHITESPACE_IN_TITLE;
-
- if(!htsmsg_get_u32(m, "title-dir", &u32) && u32)
- cfg->dvr_flags |= DVR_DIR_PER_TITLE;
+static const void *
+dvr_entry_class_channel_name_get(void *o)
+{
+ static const char *ret;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_channel)
+ ret = channel_get_name(de->de_channel);
+ else
+ ret = de->de_channel_name;
+ return &ret;
+}
+
+htsmsg_t *
+dvr_entry_class_pri_list ( void *o )
+{
+ static const struct strtab tab[] = {
+ { "Not set", -1 },
+ { "Important", DVR_PRIO_IMPORTANT },
+ { "High", DVR_PRIO_HIGH, },
+ { "Normal", DVR_PRIO_NORMAL },
+ { "Low", DVR_PRIO_LOW },
+ { "Unimportant", DVR_PRIO_UNIMPORTANT },
+ };
+ return strtab2htsmsg(tab);
+}
+
+static htsmsg_t *
+dvr_entry_class_mc_list ( void *o )
+{
+ static const struct strtab tab[] = {
+ { "Not set", -1 },
+ { "Matroska (mkv)", MC_MATROSKA, },
+ { "Same as source (pass through)", MC_PASS, },
+#if ENABLE_LIBAV
+ { "MPEG-TS", MC_MPEGTS },
+ { "MPEG-PS (DVD)", MC_MPEGPS },
+#endif
+ };
+ return strtab2htsmsg(tab);
+}
- if(!htsmsg_get_u32(m, "episode-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_EPISODE_IN_TITLE;
+htsmsg_t *
+dvr_entry_class_config_name_list(void *o)
+{
+ htsmsg_t *m = htsmsg_create_map();
+ htsmsg_t *p = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "idnode/load");
+ htsmsg_add_str(m, "event", "dvrconfig");
+ htsmsg_add_u32(p, "enum", 1);
+ htsmsg_add_str(p, "class", dvr_config_class.ic_class);
+ htsmsg_add_msg(m, "params", p);
+ return m;
+}
- if(!htsmsg_get_u32(m, "clean-title", &u32) && u32)
- cfg->dvr_flags |= DVR_CLEAN_TITLE;
- if(!htsmsg_get_u32(m, "tag-files", &u32) && !u32)
- cfg->dvr_flags &= ~DVR_TAG_FILES;
+static int
+dvr_entry_class_autorec_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ dvr_autorec_entry_t *dae = v ? dvr_autorec_find_by_uuid(v) : NULL;
+ if (dae == NULL) {
+ if (de->de_autorec) {
+ LIST_REMOVE(de, de_autorec_link);
+ de->de_autorec = NULL;
+ return 1;
+ }
+ } else if (de->de_autorec != dae) {
+ de->de_autorec = dae;
+ LIST_INSERT_HEAD(&dae->dae_spawns, de, de_autorec_link);
+ return 1;
+ }
+ return 0;
+}
- if(!htsmsg_get_u32(m, "skip-commercials", &u32) && !u32)
- cfg->dvr_flags &= ~DVR_SKIP_COMMERCIALS;
+static const void *
+dvr_entry_class_autorec_get(void *o)
+{
+ static const char *ret;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_autorec)
+ ret = idnode_uuid_as_str(&de->de_autorec->dae_id);
+ else
+ ret = "";
+ return &ret;
+}
+
+static char *
+dvr_entry_class_autorec_rend(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *s = "";
+ if (de->de_autorec) {
+ if (de->de_autorec->dae_name != NULL &&
+ de->de_autorec->dae_name[0] != '\0')
+ s = de->de_autorec->dae_name;
+ else if (de->de_autorec->dae_comment != NULL &&
+ de->de_autorec->dae_comment[0] != '\0')
+ s = de->de_autorec->dae_comment;
+ else
+ s = de->de_autorec->dae_title;
+ }
+ return strdup(s);
+}
- if(!htsmsg_get_u32(m, "subtitle-in-title", &u32) && u32)
- cfg->dvr_flags |= DVR_SUBTITLE_IN_TITLE;
+static int
+dvr_entry_class_broadcast_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ uint32_t id = *(uint32_t *)v;
+ epg_broadcast_t *bcast = epg_broadcast_find_by_id(id, de->de_channel);
+ if (bcast == NULL) {
+ if (de->de_bcast) {
+ de->de_bcast->putref((epg_object_t*)de->de_bcast);
+ de->de_bcast = NULL;
+ return 1;
+ }
+ } else if (de->de_bcast != bcast) {
+ if (de->de_bcast)
+ de->de_bcast->putref((epg_object_t*)de->de_bcast);
+ de->de_bcast = bcast;
+ de->de_bcast->getref((epg_object_t*)bcast);
+ return 1;
+ }
+ return 0;
+}
- if(!htsmsg_get_u32(m, "episode-before-date", &u32) && u32)
- cfg->dvr_flags |= DVR_EPISODE_BEFORE_DATE;
+static const void *
+dvr_entry_class_broadcast_get(void *o)
+{
+ static uint32_t id;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_bcast)
+ id = de->de_bcast->id;
+ else
+ id = 0;
+ return &id;
+}
- if(!htsmsg_get_u32(m, "episode-duplicate-detection", &u32) && u32)
- cfg->dvr_flags |= DVR_EPISODE_DUPLICATE_DETECTION;
+static char *
+dvr_entry_class_broadcast_rend(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *s = "";
+ if (de->de_bcast)
+ s = lang_str_get(de->de_bcast->summary, NULL);
+ return strdup(s);
+}
- dvr_charset_update(cfg, htsmsg_get_str(m, "charset"));
+static int
+dvr_entry_class_disp_title_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *s = "";
+ if (de->de_title)
+ s = lang_str_get(de->de_title, NULL);
+ if (strcmp(s, v ?: "")) {
+ lang_str_destroy(de->de_title);
+ de->de_title = lang_str_create();
+ if (v)
+ lang_str_add(de->de_title, v, NULL, 0);
+ return 1;
+ }
+ return 0;
+}
- tvh_str_set(&cfg->dvr_postproc, htsmsg_get_str(m, "postproc"));
- }
+static const void *
+dvr_entry_class_disp_title_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ s = "";
+ if (de->de_title) {
+ s = lang_str_get(de->de_title, NULL);
+ if (s == NULL)
+ s = "";
+ }
+ return &s;
+}
- htsmsg_destroy(l);
+static const void *
+dvr_entry_class_disp_description_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ s = "";
+ if (de->de_title) {
+ s = lang_str_get(de->de_desc, NULL);
+ if (s == NULL)
+ s = "";
+ }
+ return &s;
+}
+
+static const void *
+dvr_entry_class_episode_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ static char buf[100];
+ s = "";
+ if (de->de_bcast && de->de_bcast->episode)
+ if (epg_episode_number_format(de->de_bcast->episode,
+ buf, sizeof(buf), NULL,
+ "Season %d", ".", "Episode %d", "/%d"))
+ s = buf;
+ return &s;
+}
+
+static const void *
+dvr_entry_class_url_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ static char buf[100];
+ s = "";
+ if (de->de_sched_state == DVR_COMPLETED) {
+ snprintf(buf, sizeof(buf), "dvrfile/%s", idnode_uuid_as_str(&de->de_id));
+ s = buf;
}
+ return &s;
+}
- LIST_FOREACH(cfg, &dvrconfigs, config_link) {
- if(cfg->dvr_storage == NULL || !strlen(cfg->dvr_storage)) {
- /* Try to figure out a good place to put them videos */
+static const void *
+dvr_entry_class_filesize_get(void *o)
+{
+ static int64_t size;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_sched_state == DVR_COMPLETED)
+ size = dvr_get_filesize(de);
+ else
+ size = 0;
+ return &size;
+}
- homedir = getenv("HOME");
+static const void *
+dvr_entry_class_start_real_get(void *o)
+{
+ static time_t tm;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ tm = dvr_entry_get_start_time(de);
+ return &tm;
+}
- if(homedir != NULL) {
- snprintf(buf, sizeof(buf), "%s/Videos", homedir);
- if(stat(buf, &st) == 0 && S_ISDIR(st.st_mode))
- cfg->dvr_storage = strdup(buf);
-
- else if(stat(homedir, &st) == 0 && S_ISDIR(st.st_mode))
- cfg->dvr_storage = strdup(homedir);
- else
- cfg->dvr_storage = strdup(getcwd(buf, sizeof(buf)));
- }
+static const void *
+dvr_entry_class_stop_real_get(void *o)
+{
+ static time_t tm;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ tm = dvr_entry_get_stop_time(de);
+ return &tm;
+}
- tvhlog(LOG_WARNING, "dvr",
- "Output directory for video recording is not yet configured "
- "for DVR configuration \"%s\". "
- "Defaulting to to \"%s\". "
- "This can be changed from the web user interface.",
- cfg->dvr_config_name, cfg->dvr_storage);
- }
+static const void *
+dvr_entry_class_duration_get(void *o)
+{
+ static time_t tm;
+ time_t start, stop;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ start = dvr_entry_get_start_time(de);
+ stop = dvr_entry_get_stop_time(de);
+ if (stop > start)
+ tm = stop - start;
+ else
+ tm = 0;
+ return &tm;
+}
+
+static const void *
+dvr_entry_class_status_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ static char buf[100];
+ strncpy(buf, dvr_entry_status(de), sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ s = buf;
+ return &s;
+}
+
+static const void *
+dvr_entry_class_sched_status_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ static const char *s;
+ static char buf[100];
+ strncpy(buf, dvr_entry_schedstatus(de), sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ s = buf;
+ return &s;
+}
+
+static const void *
+dvr_entry_class_channel_icon_url_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ channel_t *ch = de->de_channel;
+ static const char *s;
+ static char buf[256];
+ uint32_t id;
+ if (ch == NULL) {
+ s = "";
+ } else if ((id = imagecache_get_id(ch->ch_icon)) != 0) {
+ snprintf(buf, sizeof(buf), "imagecache/%d", id);
+ } else {
+ strncpy(buf, ch->ch_icon ?: "", sizeof(buf));
+ buf[strlen(buf)-1] = '\0';
}
-
-#if ENABLE_INOTIFY
- dvr_inotify_init();
-#endif
- dvr_autorec_init();
- dvr_db_load();
- dvr_autorec_update();
+ s = buf;
+ return &s;
+}
+
+static htsmsg_t *
+dvr_entry_class_extra_list(void *o)
+{
+ int i;
+ htsmsg_t *e, *l = htsmsg_create_list();
+ char buf[32];
+ e = htsmsg_create_map();
+ htsmsg_add_u32(e, "key", 0);
+ htsmsg_add_str(e, "val", "Not set (use channel or DVR config)");
+ htsmsg_add_msg(l, NULL, e);
+ for (i = 1; i <= 120; i++) {
+ snprintf(buf, sizeof(buf), "%d min%s", i, i > 1 ? "s" : "");
+ e = htsmsg_create_map();
+ htsmsg_add_u32(e, "key", i);
+ htsmsg_add_str(e, "val", buf);
+ htsmsg_add_msg(l, NULL, e);
+ }
+ for (i = 120; i <= 240; i += 30) {
+ if ((i % 60) == 0)
+ snprintf(buf, sizeof(buf), "%d hrs", i / 60);
+ else
+ snprintf(buf, sizeof(buf), "%d hrs %d min%s", i / 60, i % 60, (i % 60) > 0 ? "s" : "");
+ e = htsmsg_create_map();
+ htsmsg_add_u32(e, "key", i);
+ htsmsg_add_str(e, "val", buf);
+ htsmsg_add_msg(l, NULL, e);
+ }
+ return l;
}
+
+
+static htsmsg_t *
+dvr_entry_class_content_type_list(void *o)
+{
+ htsmsg_t *m = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "epg/content_type/list");
+ return m;
+}
+
+const idclass_t dvr_entry_class = {
+ .ic_class = "dvrentry",
+ .ic_caption = "DVR Entry",
+ .ic_save = dvr_entry_class_save,
+ .ic_get_title = dvr_entry_class_get_title,
+ .ic_delete = dvr_entry_class_delete,
+ .ic_properties = (const property_t[]) {
+ {
+ .type = PT_TIME,
+ .id = "start",
+ .name = "Start Time",
+ .off = offsetof(dvr_entry_t, de_start),
+ },
+ {
+ .type = PT_TIME,
+ .id = "start_extra",
+ .name = "Extra Start Time",
+ .off = offsetof(dvr_entry_t, de_start_extra),
+ .list = dvr_entry_class_extra_list,
+ .opts = PO_DURATION,
+ },
+ {
+ .type = PT_TIME,
+ .id = "start_real",
+ .name = "Scheduled Start Time",
+ .get = dvr_entry_class_start_real_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_TIME,
+ .id = "stop",
+ .name = "Stop Time",
+ .off = offsetof(dvr_entry_t, de_stop),
+ },
+ {
+ .type = PT_TIME,
+ .id = "stop_extra",
+ .name = "Extra Stop Time",
+ .off = offsetof(dvr_entry_t, de_stop_extra),
+ .list = dvr_entry_class_extra_list,
+ .opts = PO_DURATION,
+ },
+ {
+ .type = PT_TIME,
+ .id = "stop_real",
+ .name = "Scheduled Stop Time",
+ .get = dvr_entry_class_stop_real_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_TIME,
+ .id = "duration",
+ .name = "Duration",
+ .get = dvr_entry_class_duration_get,
+ .opts = PO_RDONLY | PO_NOSAVE | PO_DURATION,
+ },
+ {
+ .type = PT_STR,
+ .id = "channel",
+ .name = "Channel",
+ .set = dvr_entry_class_channel_set,
+ .get = dvr_entry_class_channel_get,
+ .list = dvr_entry_class_channel_list,
+ },
+ {
+ .type = PT_TIME,
+ .id = "channel_icon",
+ .name = "Channel Icon",
+ .get = dvr_entry_class_channel_icon_url_get,
+ .opts = PO_HIDDEN | PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_STR,
+ .id = "channelname",
+ .name = "Channel Name",
+ .get = dvr_entry_class_channel_name_get,
+ .set = dvr_entry_class_channel_name_set,
+ .off = offsetof(dvr_entry_t, de_channel_name),
+ },
+ {
+ .type = PT_LANGSTR,
+ .id = "title",
+ .name = "Title",
+ .off = offsetof(dvr_entry_t, de_title),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "disp_title",
+ .name = "Title",
+ .get = dvr_entry_class_disp_title_get,
+ .set = dvr_entry_class_disp_title_set,
+ .opts = PO_NOSAVE,
+ },
+ {
+ .type = PT_LANGSTR,
+ .id = "description",
+ .name = "Description",
+ .off = offsetof(dvr_entry_t, de_desc),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "disp_description",
+ .name = "Description",
+ .get = dvr_entry_class_disp_description_get,
+ .opts = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
+ },
+ {
+ .type = PT_INT,
+ .id = "pri",
+ .name = "Priority",
+ .off = offsetof(dvr_entry_t, de_pri),
+ .def.i = DVR_PRIO_NORMAL,
+ .list = dvr_entry_class_pri_list,
+ },
+ {
+ .type = PT_INT,
+ .id = "container",
+ .name = "Container",
+ .off = offsetof(dvr_entry_t, de_mc),
+ .def.i = MC_MATROSKA,
+ .list = dvr_entry_class_mc_list,
+ },
+ {
+ .type = PT_STR,
+ .id = "config_name",
+ .name = "DVR Configuration",
+ .set = dvr_entry_class_config_name_set,
+ .list = dvr_entry_class_config_name_list,
+ .off = offsetof(dvr_entry_t, de_config_name),
+ },
+ {
+ .type = PT_STR,
+ .id = "creator",
+ .name = "Creator",
+ .off = offsetof(dvr_entry_t, de_creator),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "filename",
+ .name = "Filename",
+ .off = offsetof(dvr_entry_t, de_filename),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_U32,
+ .id = "errorcode",
+ .name = "Error Code",
+ .off = offsetof(dvr_entry_t, de_last_error),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_U32,
+ .id = "errors",
+ .name = "Errors",
+ .off = offsetof(dvr_entry_t, de_errors),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_U16,
+ .id = "dvb_eid",
+ .name = "DVB EPG ID",
+ .off = offsetof(dvr_entry_t, de_dvb_eid),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "noresched",
+ .name = "Do Not Reschedule",
+ .off = offsetof(dvr_entry_t, de_dvb_eid),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "autorec",
+ .name = "Auto Record",
+ .set = dvr_entry_class_autorec_set,
+ .get = dvr_entry_class_autorec_get,
+ .rend = dvr_entry_class_autorec_rend,
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_U32,
+ .id = "content_type",
+ .name = "Content Type",
+ .list = dvr_entry_class_content_type_list,
+ .off = offsetof(dvr_entry_t, de_content_type),
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_U32,
+ .id = "broadcast",
+ .name = "Broadcast Type",
+ .set = dvr_entry_class_broadcast_set,
+ .get = dvr_entry_class_broadcast_get,
+ .rend = dvr_entry_class_broadcast_rend,
+ .opts = PO_RDONLY,
+ },
+ {
+ .type = PT_STR,
+ .id = "episode",
+ .name = "Episode",
+ .get = dvr_entry_class_episode_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_STR,
+ .id = "url",
+ .name = "URL",
+ .get = dvr_entry_class_url_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_S64,
+ .id = "filesize",
+ .name = "File Size",
+ .get = dvr_entry_class_filesize_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_STR,
+ .id = "status",
+ .name = "Status",
+ .get = dvr_entry_class_status_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ },
+ {
+ .type = PT_STR,
+ .id = "sched_status",
+ .name = "Schedule Status",
+ .get = dvr_entry_class_sched_status_get,
+ .opts = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
+ },
+ {}
+ }
+};
/**
*
*/
void
-dvr_done(void)
+dvr_destroy_by_channel(channel_t *ch, int delconf)
{
- dvr_config_t *cfg;
dvr_entry_t *de;
-#if ENABLE_INOTIFY
- dvr_inotify_done();
-#endif
- pthread_mutex_lock(&global_lock);
- while ((cfg = LIST_FIRST(&dvrconfigs)) != NULL) {
- LIST_REMOVE(cfg, config_link);
- free(cfg->dvr_charset_id);
- free(cfg->dvr_charset);
- free(cfg->dvr_storage);
- free(cfg->dvr_config_name);
- free(cfg);
+ while((de = LIST_FIRST(&ch->ch_dvrs)) != NULL) {
+ LIST_REMOVE(de, de_channel_link);
+ de->de_channel = NULL;
+ de->de_channel_name = strdup(channel_get_name(ch));
+ dvr_entry_purge(de, delconf);
}
- while ((de = LIST_FIRST(&dvrentries)) != NULL)
- dvr_entry_remove(de, 0);
- pthread_mutex_unlock(&global_lock);
- dvr_autorec_done();
}
/**
cfg = dvr_config_find_by_name(name);
if (cfg == NULL) {
- tvhlog(LOG_WARNING, "dvr", "Configuration '%s' not found", name);
+ if (name && name[0])
+ tvhlog(LOG_WARNING, "dvr", "Configuration '%s' not found", name);
+ cfg = dvr_config_find_by_name("");
+ } else if (!cfg->dvr_enabled) {
+ tvhlog(LOG_WARNING, "dvr", "Configuration '%s' not enabled", name);
cfg = dvr_config_find_by_name("");
}
if (cfg == NULL) {
- cfg = dvr_config_create("");
+ cfg = dvr_config_create("", NULL, NULL);
+ dvr_config_save(cfg);
}
return cfg;
* to avoid duplicates
*/
dvr_config_t *
-dvr_config_create(const char *name)
+dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
{
dvr_config_t *cfg;
if (name == NULL)
name = "";
- tvhlog(LOG_INFO, "dvr", "Creating new configuration '%s'", name);
-
cfg = calloc(1, sizeof(dvr_config_t));
- cfg->dvr_config_name = strdup(name);
+
+ if (idnode_insert(&cfg->dvr_id, uuid, &dvr_config_class, 0)) {
+ if (uuid)
+ tvherror("dvr", "invalid config uuid '%s'", uuid);
+ free(cfg);
+ return NULL;
+ }
+
+ cfg->dvr_enabled = 1;
+ if (name)
+ cfg->dvr_config_name = strdup(name);
cfg->dvr_retention_days = 31;
cfg->dvr_mc = MC_MATROSKA;
cfg->dvr_flags = DVR_TAG_FILES | DVR_SKIP_COMMERCIALS;
cfg->dvr_muxcnf.m_file_permissions = 0664;
cfg->dvr_muxcnf.m_directory_permissions = 0775;
+
+ if (conf)
+ idnode_load(&cfg->dvr_id, conf);
+ tvhlog(LOG_INFO, "dvr", "Creating new configuration '%s'", cfg->dvr_config_name);
+
LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
- return LIST_FIRST(&dvrconfigs);
+ return cfg;
}
/**
- *
+ * destroy a dvr config
*/
-void
-dvr_config_delete(const char *name)
+static void
+dvr_config_destroy(dvr_config_t *cfg, int delconf)
{
- dvr_config_t *cfg;
-
- if (name == NULL || strlen(name) == 0) {
- tvhlog(LOG_WARNING,"dvr","Attempt to delete default config ignored");
- return;
- }
-
- cfg = dvr_config_find_by_name(name);
- if (cfg != NULL) {
+ if (delconf) {
tvhlog(LOG_INFO, "dvr", "Deleting configuration '%s'",
- cfg->dvr_config_name);
- hts_settings_remove("dvr/config%s", cfg->dvr_config_name);
- LIST_REMOVE(cfg, config_link);
- free(cfg->dvr_charset_id);
- free(cfg->dvr_charset);
- free(cfg->dvr_storage);
- free(cfg->dvr_config_name);
- free(cfg);
-
- dvrconfig_changed();
+ cfg->dvr_config_name);
+ hts_settings_remove("dvr/config/%s", idnode_uuid_as_str(&cfg->dvr_id));
}
-}
-
-/**
- *
- */
-static void
-dvr_save(dvr_config_t *cfg)
-{
- htsmsg_t *m = htsmsg_create_map();
- char buffer[5]; // Permissions buffer: leading zero, three octal digits plus terminating null
-
- if (cfg->dvr_config_name != NULL && strlen(cfg->dvr_config_name) != 0)
- htsmsg_add_str(m, "config_name", cfg->dvr_config_name);
- htsmsg_add_str(m, "storage", cfg->dvr_storage);
-
-/* Convert permissions to 0xxx octal format and output */
-
- snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_file_permissions);
- htsmsg_add_str(m, "file-permissions", buffer);
-
- snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_directory_permissions);
- htsmsg_add_str(m, "directory-permissions", buffer);
-
- htsmsg_add_u32(m, "container", cfg->dvr_mc);
- htsmsg_add_u32(m, "cache", cfg->dvr_muxcnf.m_cache);
- htsmsg_add_u32(m, "rewrite-pat",
- !!(cfg->dvr_muxcnf.m_flags & MC_REWRITE_PAT));
- htsmsg_add_u32(m, "rewrite-pmt",
- !!(cfg->dvr_muxcnf.m_flags & MC_REWRITE_PMT));
- htsmsg_add_u32(m, "retention-days", cfg->dvr_retention_days);
- htsmsg_add_u32(m, "pre-extra-time", cfg->dvr_extra_time_pre);
- htsmsg_add_u32(m, "post-extra-time", cfg->dvr_extra_time_post);
- htsmsg_add_u32(m, "day-dir", !!(cfg->dvr_flags & DVR_DIR_PER_DAY));
- htsmsg_add_u32(m, "channel-dir", !!(cfg->dvr_flags & DVR_DIR_PER_CHANNEL));
- htsmsg_add_u32(m, "channel-in-title", !!(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE));
- htsmsg_add_u32(m, "date-in-title", !!(cfg->dvr_flags & DVR_DATE_IN_TITLE));
- htsmsg_add_u32(m, "time-in-title", !!(cfg->dvr_flags & DVR_TIME_IN_TITLE));
- htsmsg_add_u32(m, "whitespace-in-title", !!(cfg->dvr_flags & DVR_WHITESPACE_IN_TITLE));
- htsmsg_add_u32(m, "title-dir", !!(cfg->dvr_flags & DVR_DIR_PER_TITLE));
- htsmsg_add_u32(m, "episode-in-title", !!(cfg->dvr_flags & DVR_EPISODE_IN_TITLE));
- htsmsg_add_u32(m, "clean-title", !!(cfg->dvr_flags & DVR_CLEAN_TITLE));
- htsmsg_add_u32(m, "tag-files", !!(cfg->dvr_flags & DVR_TAG_FILES));
- htsmsg_add_u32(m, "skip-commercials", !!(cfg->dvr_flags & DVR_SKIP_COMMERCIALS));
- htsmsg_add_u32(m, "subtitle-in-title", !!(cfg->dvr_flags & DVR_SUBTITLE_IN_TITLE));
- htsmsg_add_u32(m, "episode-before-date", !!(cfg->dvr_flags & DVR_EPISODE_BEFORE_DATE));
- htsmsg_add_u32(m, "episode-duplicate-detection", !!(cfg->dvr_flags & DVR_EPISODE_DUPLICATE_DETECTION));
- if (cfg->dvr_charset != NULL)
- htsmsg_add_str(m, "charset", cfg->dvr_charset);
- if (cfg->dvr_postproc != NULL)
- htsmsg_add_str(m, "postproc", cfg->dvr_postproc);
-
- hts_settings_save(m, "dvr/config%s", cfg->dvr_config_name);
- htsmsg_destroy(m);
-
- dvrconfig_changed();
-}
-
-/**
- *
- */
-void
-dvr_storage_set(dvr_config_t *cfg, const char *storage)
-{
- if(cfg->dvr_storage != NULL && !strcmp(cfg->dvr_storage, storage))
- return;
-
- tvh_str_set(&cfg->dvr_storage, storage);
- dvr_save(cfg);
+ LIST_REMOVE(cfg, config_link);
+ idnode_unlink(&cfg->dvr_id);
+ free(cfg->dvr_charset_id);
+ free(cfg->dvr_charset);
+ free(cfg->dvr_storage);
+ free(cfg->dvr_config_name);
+ free(cfg);
}
/**
*
*/
void
-dvr_charset_set(dvr_config_t *cfg, const char *charset)
+dvr_config_delete(const char *name)
{
- if(cfg->dvr_charset != NULL && !strcmp(cfg->dvr_charset, charset))
- return;
-
- dvr_charset_update(cfg, charset);
- dvr_save(cfg);
-}
+ dvr_config_t *cfg;
-/**
- *
- */
-void
-dvr_file_permissions_set(dvr_config_t *cfg, int permissions)
-{
- if(cfg->dvr_muxcnf.m_file_permissions == permissions)
+ if (name == NULL || strlen(name) == 0) {
+ tvhlog(LOG_WARNING,"dvr","Attempt to delete default config ignored");
return;
+ }
- cfg->dvr_muxcnf.m_file_permissions = permissions;
- dvr_save(cfg);
+ cfg = dvr_config_find_by_name(name);
+ if (cfg != NULL)
+ dvr_config_destroy(cfg, 1);
}
-/**
+/*
*
*/
-void
-dvr_directory_permissions_set(dvr_config_t *cfg, int permissions)
-{
- if(cfg->dvr_muxcnf.m_directory_permissions == permissions)
- return;
-
- cfg->dvr_muxcnf.m_directory_permissions = permissions;
- dvr_save(cfg);
+static void
+dvr_config_update_flags(dvr_config_t *cfg)
+{
+ int r = 0;
+ if (cfg->dvr_dir_per_day) r |= DVR_DIR_PER_DAY;
+ if (cfg->dvr_channel_dir) r |= DVR_DIR_PER_CHANNEL;
+ if (cfg->dvr_channel_in_title) r |= DVR_CHANNEL_IN_TITLE;
+ if (cfg->dvr_date_in_title) r |= DVR_DATE_IN_TITLE;
+ if (cfg->dvr_time_in_title) r |= DVR_TIME_IN_TITLE;
+ if (cfg->dvr_whitespace_in_title) r |= DVR_WHITESPACE_IN_TITLE;
+ if (cfg->dvr_title_dir) r |= DVR_DIR_PER_TITLE;
+ if (cfg->dvr_episode_in_title) r |= DVR_EPISODE_IN_TITLE;
+ if (cfg->dvr_clean_title) r |= DVR_CLEAN_TITLE;
+ if (cfg->dvr_tag_files) r |= DVR_TAG_FILES;
+ if (cfg->dvr_skip_commercials) r |= DVR_SKIP_COMMERCIALS;
+ if (cfg->dvr_subtitle_in_title) r |= DVR_SUBTITLE_IN_TITLE;
+ if (cfg->dvr_episode_before_date) r |= DVR_EPISODE_BEFORE_DATE;
+ if (cfg->dvr_episode_duplicate) r |= DVR_EPISODE_DUPLICATE_DETECTION;
+ cfg->dvr_flags = r;
+ r = 0;
+ if (cfg->dvr_rewrite_pat) r |= MC_REWRITE_PAT;
+ if (cfg->dvr_rewrite_pmt) r |= MC_REWRITE_PMT;
+ cfg->dvr_muxcnf.m_flags = r;
}
-/**
- *
- */
-void
-dvr_container_set(dvr_config_t *cfg, const char *container)
-{
- muxer_container_type_t mc;
-
- mc = muxer_container_txt2type(container);
- if(mc == MC_UNKNOWN)
- mc = MC_MATROSKA;
-
- if(cfg->dvr_mc == mc)
- return;
-
- cfg->dvr_mc = mc;
- dvr_save(cfg);
-}
-
-/**
+/*
*
*/
void
-dvr_mux_cache_set(dvr_config_t *cfg, int mcache)
+dvr_config_save(dvr_config_t *cfg)
{
- if (mcache < MC_CACHE_UNKNOWN || mcache > MC_CACHE_LAST)
- mcache = MC_CACHE_UNKNOWN;
-
- if(cfg->dvr_muxcnf.m_cache == mcache)
- return;
+ htsmsg_t *m = htsmsg_create_map();
- cfg->dvr_muxcnf.m_cache = mcache;
+ lock_assert(&global_lock);
- dvr_save(cfg);
+ idnode_save(&cfg->dvr_id, m);
+ hts_settings_save(m, "dvr/config/%s", idnode_uuid_as_str(&cfg->dvr_id));
+ htsmsg_destroy(m);
}
+/* **************************************************************************
+ * DVR Config Class definition
+ * **************************************************************************/
-/**
- *
- */
-void
-dvr_postproc_set(dvr_config_t *cfg, const char *postproc)
+static void
+dvr_config_class_save(idnode_t *self)
{
- if(cfg->dvr_postproc != NULL && !strcmp(cfg->dvr_postproc, postproc))
- return;
-
- tvh_str_set(&cfg->dvr_postproc, !strcmp(postproc, "") ? NULL : postproc);
- dvr_save(cfg);
+ dvr_config_update_flags((dvr_config_t *)self);
+ dvr_config_save((dvr_config_t *)self);
}
-
-/**
- *
- */
-void
-dvr_retention_set(dvr_config_t *cfg, int days)
+static void
+dvr_config_class_delete(idnode_t *self)
{
- dvr_entry_t *de;
- if(days < 1 || cfg->dvr_retention_days == days)
- return;
-
- cfg->dvr_retention_days = days;
-
- /* Also, rearm all timers */
-
- LIST_FOREACH(de, &dvrentries, de_global_link)
- if(de->de_sched_state == DVR_COMPLETED)
- gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de,
- de->de_stop + cfg->dvr_retention_days * 86400);
- dvr_save(cfg);
+ dvr_config_destroy((dvr_config_t *)self, 1);
}
-
-/**
- *
- */
-void
-dvr_flags_set(dvr_config_t *cfg, int flags)
+static int
+dvr_config_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write)
{
- if(cfg->dvr_flags == flags)
- return;
-
- cfg->dvr_flags = flags;
- dvr_save(cfg);
+ dvr_config_t *cfg = (dvr_config_t *)self;
+ if (access_verify2(a, ACCESS_RECORDER))
+ return -1;
+ if (access_verify2(a, ACCESS_ADMIN) && access_verify2(a, ACCESS_RECORDER_ALL))
+ return 0;
+ if (strcmp(cfg->dvr_config_name ?: "", a->aa_username ?: ""))
+ return -1;
+ return 0;
}
-/**
- *
- */
-void
-dvr_mux_flags_set(dvr_config_t *cfg, int flags)
+static const char *
+dvr_config_class_get_title (idnode_t *self)
{
- if(cfg->dvr_muxcnf.m_flags == flags)
- return;
-
- cfg->dvr_muxcnf.m_flags = flags;
- dvr_save(cfg);
+ dvr_config_t *cfg = (dvr_config_t *)self;
+ if (cfg->dvr_config_name && cfg->dvr_config_name[0] != '\0')
+ return cfg->dvr_config_name;
+ return "(Default Profile)";
}
-
-/**
- *
- */
-void
-dvr_extra_time_pre_set(dvr_config_t *cfg, int d)
+static int
+dvr_config_class_charset_set(void *o, const void *v)
{
- if(cfg->dvr_extra_time_pre == d)
- return;
-
- cfg->dvr_extra_time_pre = d;
- dvr_save(cfg);
+ dvr_config_t *cfg = (dvr_config_t *)o;
+ return dvr_charset_update(cfg, v);
}
-
-/**
- *
- */
-void
-dvr_extra_time_post_set(dvr_config_t *cfg, int d)
+static htsmsg_t *
+dvr_config_class_charset_list(void *o)
{
- if(cfg->dvr_extra_time_post == d)
- return;
-
- cfg->dvr_extra_time_post = d;
- dvr_save(cfg);
-}
-
+ htsmsg_t *m = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "intlconv/charsets");
+ htsmsg_add_str(m, "event", "charsets");
+ return m;
+}
+
+static htsmsg_t *
+dvr_config_class_cache_list(void *o)
+{
+ static struct strtab tab[] = {
+ { "Unknown", MC_CACHE_UNKNOWN },
+ { "System", MC_CACHE_SYSTEM },
+ { "Do not keep", MC_CACHE_DONTKEEP },
+ { "Sync", MC_CACHE_SYNC },
+ { "Sync + Do not keep", MC_CACHE_SYNCDONTKEEP }
+ };
+ return strtab2htsmsg(tab);
+}
+
+const idclass_t dvr_config_class = {
+ .ic_class = "dvrconfig",
+ .ic_caption = "DVR Configuration Profile",
+ .ic_save = dvr_config_class_save,
+ .ic_get_title = dvr_config_class_get_title,
+ .ic_delete = dvr_config_class_delete,
+ .ic_perm = dvr_config_class_perm,
+ .ic_groups = (const property_group_t[]) {
+ {
+ .name = "DVR Behaviour",
+ .number = 1,
+ },
+ {
+ .name = "Recording File Options",
+ .number = 2,
+ },
+ {
+ .name = "Subdirectory Options",
+ .number = 3,
+ },
+ {
+ .name = "Filename Options",
+ .number = 4,
+ .column = 1,
+ },
+ {
+ .name = "",
+ .number = 5,
+ .parent = 4,
+ .column = 2,
+ },
+ {}
+ },
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = "Enabled",
+ .off = offsetof(dvr_config_t, dvr_enabled),
+ .def.i = 1,
+ .group = 1,
+ },
+ {
+ .type = PT_STR,
+ .id = "name",
+ .name = "Config Name",
+ .off = offsetof(dvr_config_t, dvr_config_name),
+ .def.s = "! New config",
+ .group = 1,
+ },
+ {
+ .type = PT_INT,
+ .id = "container",
+ .name = "Container",
+ .off = offsetof(dvr_config_t, dvr_mc),
+ .def.i = MC_MATROSKA,
+ .list = dvr_entry_class_mc_list,
+ .group = 1,
+ },
+ {
+ .type = PT_INT,
+ .id = "cache",
+ .name = "Cache Scheme",
+ .off = offsetof(dvr_config_t, dvr_muxcnf.m_cache),
+ .def.i = MC_CACHE_DONTKEEP,
+ .list = dvr_config_class_cache_list,
+ .group = 1,
+ },
+ {
+ .type = PT_U32,
+ .id = "retention-days",
+ .name = "DVR Log Retention Time (days)",
+ .off = offsetof(dvr_config_t, dvr_retention_days),
+ .def.u32 = 31,
+ .group = 1,
+ },
+ {
+ .type = PT_U32,
+ .id = "pre-extra-time",
+ .name = "Extra Time Before Recordings (minutes)",
+ .off = offsetof(dvr_config_t, dvr_extra_time_pre),
+ .group = 1,
+ },
+ {
+ .type = PT_U32,
+ .id = "post-extra-time",
+ .name = "Extra Time After Recordings (minutes)",
+ .off = offsetof(dvr_config_t, dvr_extra_time_post),
+ .group = 1,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "episode-duplicate-detection",
+ .name = "Episode Duplicate Detect",
+ .off = offsetof(dvr_config_t, dvr_episode_duplicate),
+ .group = 1,
+ },
+ {
+ .type = PT_STR,
+ .id = "postproc",
+ .name = "Post-Processor Command",
+ .off = offsetof(dvr_config_t, dvr_postproc),
+ .group = 1,
+ },
+ {
+ .type = PT_STR,
+ .id = "storage",
+ .name = "Recording System Path",
+ .off = offsetof(dvr_config_t, dvr_storage),
+ .group = 2,
+ },
+ {
+ .type = PT_PERM,
+ .id = "file-permissions",
+ .name = "File Permissions (octal, e.g. 0664)",
+ .off = offsetof(dvr_config_t, dvr_muxcnf.m_file_permissions),
+ .def.u32 = 0664,
+ .group = 2,
+ },
+ {
+ .type = PT_STR,
+ .id = "charset",
+ .name = "Filename Charset",
+ .off = offsetof(dvr_config_t, dvr_charset),
+ .set = dvr_config_class_charset_set,
+ .list = dvr_config_class_charset_list,
+ .def.s = "UTF-8",
+ .group = 2,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "rewrite-pat",
+ .name = "Rewrite PAT",
+ .off = offsetof(dvr_config_t, dvr_rewrite_pat),
+ .def.i = 1,
+ .group = 2,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "rewrite-pmt",
+ .name = "Rewrite PMT",
+ .off = offsetof(dvr_config_t, dvr_rewrite_pmt),
+ .group = 2,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "tag-files",
+ .name = "Tag Files With Metadata",
+ .off = offsetof(dvr_config_t, dvr_tag_files),
+ .def.i = 1,
+ .group = 2,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "skip-commercials",
+ .name = "Skip Commercials",
+ .off = offsetof(dvr_config_t, dvr_skip_commercials),
+ .def.i = 1,
+ .group = 2,
+ },
+ {
+ .type = PT_PERM,
+ .id = "directory-permissions",
+ .name = "Directory Permissions (octal, e.g. 0775)",
+ .off = offsetof(dvr_config_t, dvr_muxcnf.m_directory_permissions),
+ .def.u32 = 0775,
+ .group = 3,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "day-dir",
+ .name = "Make Subdirectories Per Day",
+ .off = offsetof(dvr_config_t, dvr_dir_per_day),
+ .group = 3,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "channel-dir",
+ .name = "Make Subdirectories Per Channel",
+ .off = offsetof(dvr_config_t, dvr_channel_dir),
+ .group = 3,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "title-dir",
+ .name = "Make Subdirectories Per Title",
+ .off = offsetof(dvr_config_t, dvr_title_dir),
+ .group = 3,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "channel-in-title",
+ .name = "Include Channel Name In Filename",
+ .off = offsetof(dvr_config_t, dvr_channel_in_title),
+ .group = 4,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "date-in-title",
+ .name = "Include Date In Filename",
+ .off = offsetof(dvr_config_t, dvr_date_in_title),
+ .group = 4,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "time-in-title",
+ .name = "Include Time In Filename",
+ .off = offsetof(dvr_config_t, dvr_time_in_title),
+ .group = 4,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "episode-in-title",
+ .name = "Include Episode In Filename",
+ .off = offsetof(dvr_config_t, dvr_time_in_title),
+ .group = 4,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "subtitle-in-title",
+ .name = "Include Subtitle In Filename",
+ .off = offsetof(dvr_config_t, dvr_time_in_title),
+ .group = 5,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "episode-before-date",
+ .name = "Put Episode In Filename Before Date And Time",
+ .off = offsetof(dvr_config_t, dvr_episode_before_date),
+ .group = 5,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "clean-title",
+ .name = "Remove All Unsafe Characters From Filename",
+ .off = offsetof(dvr_config_t, dvr_clean_title),
+ .group = 5,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "whitespace-in-title",
+ .name = "Replace Whitespace In Title with '-'",
+ .off = offsetof(dvr_config_t, dvr_whitespace_in_title),
+ .group = 5,
+ },
+ {}
+ },
+};
/**
*
/* Also delete directories, if they were created for the recording and if they are empty */
- dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
+ dvr_config_t *cfg = de->de_config;
char path[500];
snprintf(path, sizeof(path), "%s", cfg->dvr_storage);
abort();
}
}
+
+/**
+ *
+ */
+void
+dvr_init(void)
+{
+ htsmsg_t *m, *l;
+ htsmsg_field_t *f;
+ char buf[500];
+ const char *homedir;
+ struct stat st;
+ dvr_config_t *cfg;
+
+ dvr_iov_max = sysconf(_SC_IOV_MAX);
+
+ /* Default settings */
+
+ LIST_INIT(&dvrconfigs);
+
+ if ((l = hts_settings_load("dvr/config")) != NULL) {
+ HTSMSG_FOREACH(f, l) {
+ if ((m = htsmsg_get_map_by_field(f)) == NULL) continue;
+ (void)dvr_config_create(NULL, f->hmf_name, m);
+ }
+ htsmsg_destroy(l);
+ }
+
+ /* Create the default entry */
+
+ cfg = dvr_config_find_by_name_default("");
+ assert(cfg);
+
+ LIST_FOREACH(cfg, &dvrconfigs, config_link) {
+ if(cfg->dvr_storage == NULL || !strlen(cfg->dvr_storage)) {
+ /* Try to figure out a good place to put them videos */
+
+ homedir = getenv("HOME");
+
+ if(homedir != NULL) {
+ snprintf(buf, sizeof(buf), "%s/Videos", homedir);
+ if(stat(buf, &st) == 0 && S_ISDIR(st.st_mode))
+ cfg->dvr_storage = strdup(buf);
+
+ else if(stat(homedir, &st) == 0 && S_ISDIR(st.st_mode))
+ cfg->dvr_storage = strdup(homedir);
+ else
+ cfg->dvr_storage = strdup(getcwd(buf, sizeof(buf)));
+ }
+
+ tvhlog(LOG_WARNING, "dvr",
+ "Output directory for video recording is not yet configured "
+ "for DVR configuration \"%s\". "
+ "Defaulting to to \"%s\". "
+ "This can be changed from the web user interface.",
+ cfg->dvr_config_name, cfg->dvr_storage);
+ }
+ }
+
+#if ENABLE_INOTIFY
+ dvr_inotify_init();
+#endif
+ dvr_autorec_init();
+ dvr_db_load();
+ dvr_autorec_update();
+}
+
+/**
+ *
+ */
+void
+dvr_done(void)
+{
+ dvr_config_t *cfg;
+ dvr_entry_t *de;
+
+#if ENABLE_INOTIFY
+ dvr_inotify_done();
+#endif
+ pthread_mutex_lock(&global_lock);
+ while ((cfg = LIST_FIRST(&dvrconfigs)) != NULL)
+ dvr_config_destroy(cfg, 0);
+ while ((de = LIST_FIRST(&dvrentries)) != NULL)
+ dvr_entry_remove(de, 0);
+ pthread_mutex_unlock(&global_lock);
+ dvr_autorec_done();
+}
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;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
muxer_container_type_t mc;
- mc = de->de_mc;
+ mc = dvr_entry_get_mc(de);
de->de_mux = muxer_create(mc, &cfg->dvr_muxcnf);
if(!de->de_mux) {
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);
}
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));
}
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)))
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 {
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:
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);
}
if(http_access_verify(hc, hp->hp_accessmask))
err = HTTP_STATUS_UNAUTHORIZED;
- else
+ else {
+ /* FIXME: Recode to obtain access only once */
+ hc->hc_access = access_get(hc->hc_username, hc->hc_password,
+ (struct sockaddr *)hc->hc_peer);
err = hp->hp_callback(hc, remain, hp->hp_opaque);
+ access_destroy(hc->hc_access);
+ hc->hc_access = NULL;
+ }
if(err == -1)
return 1;
#include "htsbuf.h"
#include "url.h"
#include "tvhpoll.h"
+#include "access.h"
struct channel;
char *hc_username;
char *hc_password;
+ access_t *hc_access;
struct config_head *hc_user_config;
*u32 = *(int*)ptr;
return 0;
case PT_U16:
+ *u32 = *(uint16_t*)ptr;
+ return 0;
+ case PT_U32:
*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:
- *u32 = *(uint16_t*)ptr;
+ *s64 = *(uint32_t*)ptr;
+ return 0;
+ case PT_S64:
+ *s64 = *(int64_t*)ptr;
return 0;
default:
break;
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) {
+ void *ptr = self;
+ ptr += p->off;
+ switch (p->type) {
+ case PT_TIME:
+ *tm = *(time_t*)ptr;
+ return 0;
+ default:
+ break;
+ }
+ }
+ return 1;
+}
+
/* **************************************************************************
* Lookup
* *************************************************************************/
{
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_U16:
case PT_U32:
case PT_BOOL:
+ case PT_PERM:
{
uint32_t u32a = 0, u32b = 0;
idnode_get_u32(ina, sort->key, &u32a);
return 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 s64a - s64b;
+ else
+ return s64b - s64a;
+ }
+ break;
case PT_DBL:
// TODO
+ 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 ta - tb;
+ else
+ return tb - ta;
+ }
+ break;
+ case PT_LANGSTR:
+ // TODO?
case PT_NONE:
break;
}
* 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);
}
/**
*/
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
#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;
}
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 )
{
* 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();
htsmsg_add_str(m, "class", 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;
*
*/
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;
if ((s = idclass_get_class(idc)))
htsmsg_add_str(m, "class", s);
- htsmsg_add_msg(m, "params", idnode_params(idc, self, optmask));
+ htsmsg_add_msg(m, "params", idnode_params(idc, self, list, optmask));
return m;
}
#include <regex.h>
-struct htsmsg;
+struct access;
typedef struct idnode idnode_t;
/*
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);
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);
};
/*
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_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);
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;
}
#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;
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);
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 )
( 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 );
/* Muxer configuration used when creating a muxer. */
typedef struct muxer_config {
int m_flags;
- muxer_cache_type_t m_cache;
+ int m_cache;
/*
* directory_permissions should really be in dvr.h as it's not really needed for the muxer
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;
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);
}
#include "tvheadend.h"
#include "prop.h"
+#include "lang_str.h"
/* **************************************************************************
* Utilities
*
*/
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 },
};
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))) {\
if (!pl) return 0;
for (p = pl; p->id; p++) {
+
if (p->type == PT_NONE) continue;
f = htsmsg_field_find(m, p->id);
/* Ignore */
if(p->opts & optmask) continue;
+ /* Sanity check */
+ assert(p->set || p->off);
+
/* Write */
save = 0;
cur = obj + p->off;
PROP_UPDATE(u32, uint32_t);
break;
}
+ case PT_S64: {
+ if (htsmsg_field_get_s64(f, &s64))
+ continue;
+ i = s64;
+ PROP_UPDATE(i, int64_t);
+ break;
+ }
case PT_DBL: {
if (htsmsg_field_get_dbl(f, &dbl))
continue;
}
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;
}
*/
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;
+ char buf[16];
/* Ignore */
if (p->opts & 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)
case PT_INT:
htsmsg_add_s64(m, name, *(int *)val);
break;
+ case PT_U16:
+ htsmsg_add_u32(m, name, *(uint16_t *)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);
+ case PT_S64:
+ htsmsg_add_s64(m, name, *(int64_t *)val);
break;
case PT_STR:
if ((s = *(const char **)val))
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;
}
*/
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);
+ }
+ }
+ }
}
/**
*
*/
-void
-prop_serialize
- (void *obj, const property_t *pl, htsmsg_t *msg, int optmask, htsmsg_t *inc)
+static void
+prop_serialize_value
+ (void *obj, const property_t *pl, htsmsg_t *msg, 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;
- }
+ char buf[16];
+
+ /* 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;
}
}
+ }
- /* Ignore */
- if (inc && !htsmsg_get_u32_or_default(inc, pl->id, 0))
- continue;
+ htsmsg_t *m = htsmsg_create_map();
- 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");
- /* 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;
+ }
- /* 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;
- }
+ /* Metadata */
+ htsmsg_add_str(m, "caption", pl->name);
+ if (pl->islist)
+ htsmsg_add_u32(m, "list", 1);
- /* 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);
+ /* 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;
+ }
- htsmsg_add_msg(msg, NULL, m);
+ /* 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);
+ if (pl->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);
+
+ /* Data */
+ if (obj)
+ prop_read_value(obj, pl, m, "value", optmask);
+
+ htsmsg_add_msg(msg, NULL, m);
+}
+
+/**
+ *
+ */
+void
+prop_serialize
+ (void *obj, const property_t *pl, htsmsg_t *msg, htsmsg_t *list, int optmask)
+{
+ if(pl == NULL)
+ return;
+
+ 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);
+ }
+ }
}
}
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
prop_type_t type; ///< Type
int islist; ///< Is a list
size_t off; ///< Offset into object
- int opts; ///< Options
+ uint32_t opts; ///< Options
+ uint32_t group; ///< Visual group ID (like ExtJS FieldSet)
/* String based processing */
const void *(*get) (void *ptr);
/* 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;
/* Notification callback */
(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__ */
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;
}
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;
-}
-
/**
*
*/
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;
-
-}
-
-
/**
*
*/
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;
}
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;
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);
- }
-}
-
/**
*
*/
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
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 %s",
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);
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:
-tvheadend.weekdays = new Ext.data.SimpleStore({
- fields: ['identifier', 'name'],
- id: 0,
- data: [['1', 'Mon'], ['2', 'Tue'], ['3', 'Wed'], ['4', 'Thu'],
- ['5', 'Fri'], ['6', 'Sat'], ['7', 'Sun']]
-});
-
-//This should be loaded from tvheadend
-tvheadend.dvrprio = new Ext.data.SimpleStore({
- fields: ['identifier', 'name'],
- id: 0,
- data: [['important', 'Important'], ['high', 'High'],
- ['normal', 'Normal'], ['low', 'Low'],
- ['unimportant', 'Unimportant']]
-});
-
-//For the container configuration
-tvheadend.containers = new Ext.data.JsonStore({
- autoLoad: true,
- root: 'entries',
- fields: ['name', 'description'],
- id: 'name',
- url: 'dvr_containers',
- baseParams: {
- op: 'list'
- }
-});
-
-//For the cache configuration
-tvheadend.charsets = new Ext.data.JsonStore({
- autoLoad: true,
- root: 'entries',
- fields: ['key', 'val'],
- id: 'key',
- url: 'api/intlconv/charsets',
- baseParams: {
- enum: 1
- }
-});
-
-//For the charset configuration
-tvheadend.caches = new Ext.data.JsonStore({
- autoLoad: true,
- root: 'entries',
- fields: ['index', 'description'],
- id: 'name',
- url: 'dvr_caches',
- baseParams: {
- op: 'list'
- }
-});
-
-/**
- * Configuration names
+/*
+ * DVR Config / Schedule / Log editor and viewer
*/
-tvheadend.configNames = new Ext.data.JsonStore({
- autoLoad: true,
- root: 'entries',
- fields: ['identifier', 'name'],
- id: 'identifier',
- url: 'confignames',
- baseParams: {
- op: 'list'
- }
-});
-
-tvheadend.configNames.setDefaultSort('name', 'ASC');
-
-tvheadend.comet.on('dvrconfig', function(m) {
- if (m.reload != null)
- tvheadend.configNames.reload();
-});
/**
*
*/
-tvheadend.dvrDetails = function(entry) {
-
- var content = '';
- var but;
-
- if (entry.chicon != null && entry.chicon.length > 0)
- content += '<img class="x-epg-chicon" src="'
- + entry.chicon + '">';
+tvheadend.dvrDetails = function(uuid) {
+
+ function showit(d) {
+ var params = d[0].params;
+ var chicon = params[0].value;
+ var title = params[1].value;
+ var desc = params[2].value;
+ var status = params[3].value;
+ var content = '';
+ var but;
+
+ if (chicon != null && chicon.length > 0)
+ content += '<img class="x-epg-chicon" src="' + chicon + '">';
+
+ content += '<div class="x-epg-title">' + title + '</div>';
+ content += '<div class="x-epg-desc">' + desc + '</div>';
+ content += '<hr>';
+ content += '<div class="x-epg-meta">Status: ' + status + '</div>';
+
+ var win = new Ext.Window({
+ title: title,
+ layout: 'fit',
+ width: 400,
+ height: 300,
+ constrainHeader: true,
+ buttonAlign: 'center',
+ html: content
+ });
- content += '<div class="x-epg-title">' + entry.title + '</div>';
- content += '<div class="x-epg-desc">' + entry.description + '</div>';
- content += '<hr>';
- content += '<div class="x-epg-meta">Status: ' + entry.status + '</div>';
+ win.show();
+ }
- var win = new Ext.Window({
- title: entry.title,
- layout: 'fit',
- width: 400,
- height: 300,
- constrainHeader: true,
- buttonAlign: 'center',
- html: content
+ tvheadend.loading(1);
+ Ext.Ajax.request({
+ url: 'api/idnode/load',
+ params: {
+ uuid: uuid,
+ list: 'channel_icon,disp_title,disp_description,status',
+ },
+ success: function(d) {
+ d = json_decode(d);
+ tvheadend.loading(0),
+ showit(d);
+ },
+ failure: function(d) {
+ tvheadend.loading(0);
+ }
});
-
- win.show();
};
-/**
- *
- */
-tvheadend.dvrschedule = function(title, iconCls, dvrStore) {
-
- var actions = new Ext.ux.grid.RowActions({
+tvheadend.dvrRowActions = function() {
+ return new Ext.ux.grid.RowActions({
+ id: 'details',
header: 'Details',
- dataIndex: 'actions',
width: 45,
actions: [
{
- iconIndex: 'schedstate'
+ iconIndex: 'sched_status'
},
{
iconCls: 'info',
qtip: 'Recording details',
cb: function(grid, rec, act, row) {
- new tvheadend.dvrDetails(grid.getStore().getAt(row).data);
+ new tvheadend.dvrDetails(grid.getStore().getAt(row).id);
}
}
]
});
+}
- function renderDate(value) {
- var dt = new Date(value);
- return dt.format('D j M H:i');
- }
-
- function renderDuration(value) {
- value = value / 60; /* Nevermind the seconds */
-
- if (value >= 60) {
- var min = parseInt(value % 60);
- var hours = parseInt(value / 60);
-
- if (min === 0) {
- return hours + ' hrs';
- }
- return hours + ' hrs, ' + min + ' min';
- }
- else {
- return parseInt(value) + ' min';
- }
- }
-
- function renderSize(value)
- {
- if (value == null)
- return '';
- return parseInt(value / 1000000) + ' MB';
- }
-
- function renderPri(value) {
- return tvheadend.dvrprio.getById(value).data.name;
- }
-
- var cols = [actions];
- if (iconCls === 'television')
- cols.push({
- width: 40,
- header: "Play",
- renderer: function(v, o, r) {
- var title = r.data['title'];
- if (r.data['episode'])
- title += ' / ' + r.data['episode'];
- return '<a href="play/dvrfile/' + r.data['id'] +
- '?title=' + encodeURIComponent(title) + '">Play</a>';
- }
- });
- cols.push({
- width: 250,
- id: 'title',
- header: "Title",
- sortable: true,
- dataIndex: 'title'
- });
- cols.push({
- width: 100,
- id: 'episode',
- header: "Episode",
- sortable: true,
- dataIndex: 'episode'
- });
- if (iconCls === 'clock')
- cols.push({
- width: 100,
- id: 'pri',
- header: "Priority",
- sortable: true,
- dataIndex: 'pri',
- renderer: renderPri,
- hidden: iconCls !== 'clock'
- });
- cols.push({
- width: 100,
- id: 'start',
- header: iconCls === 'clock' ? "Start" : "Date/Time",
- sortable: true,
- dataIndex: 'start',
- renderer: renderDate
- });
- cols.push({
- width: 100,
- hidden: true,
- id: 'end',
- header: "End",
- sortable: true,
- dataIndex: 'end',
- renderer: renderDate
- });
- cols.push({
- width: 100,
- id: 'duration',
- header: "Duration",
- sortable: true,
- dataIndex: 'duration',
- renderer: renderDuration
- });
- if (iconCls === 'television')
- cols.push({
- width: 100,
- id: 'filesize',
- header: "Filesize",
- sortable: true,
- dataIndex: 'filesize',
- renderer: renderSize,
- hidden: iconCls !== 'television'
- });
- cols.push({
- width: 250,
- id: 'channel',
- header: "Channel",
- sortable: true,
- dataIndex: 'channel'
- });
- cols.push({
- width: 200,
- id: 'creator',
- header: "Created by",
- sortable: true,
- hidden: true,
- dataIndex: 'creator'
- });
- if (iconCls === 'clock')
- cols.push({
- width: 200,
- id: 'config_name',
- header: "DVR Configuration",
- sortable: true,
- renderer: function(value, metadata, record, row, col, store) {
- if (!value) {
- return '<span class="tvh-grid-unset">(default)</span>';
- }
- else {
- return value;
- }
- },
- dataIndex: 'config_name',
- hidden: iconCls !== 'clock'
- });
- if (iconCls === 'exclamation')
- cols.push({
- width: 200,
- id: 'status',
- header: "Status",
- sortable: true,
- dataIndex: 'status',
- hidden: iconCls !== 'exclamation'
- });
-
- var dvrCm = new Ext.grid.ColumnModel({columns: cols});
-
- function addEntry() {
-
- function createRecording() {
- panel.getForm().submit({
- params: {
- 'op': 'createEntry'
- },
- url: 'dvr/addentry',
- waitMsg: 'Creating entry...',
- failure: function(response, options) {
- Ext.MessageBox.alert('Server Error', 'Unable to create entry');
- },
- success: function() {
- win.close();
- }
- });
- }
-
- var panel = new Ext.FormPanel({
- frame: true,
- border: true,
- bodyStyle: 'padding:5px',
- labelAlign: 'right',
- labelWidth: 110,
- defaultType: 'textfield',
- items: [new Ext.form.ComboBox({
- fieldLabel: 'Channel',
- name: 'channel',
- hiddenName: 'channelid',
- editable: false,
- allowBlank: false,
- displayField: 'val',
- valueField: 'key',
- mode: 'remote',
- triggerAction: 'all',
- store: tvheadend.channels
- }), new Ext.form.DateField({
- allowBlank: false,
- fieldLabel: 'Date',
- name: 'date'
- }), new Ext.form.TimeField({
- allowBlank: false,
- fieldLabel: 'Start time',
- name: 'starttime',
- increment: 10,
- format: 'H:i'
- }), new Ext.form.TimeField({
- allowBlank: false,
- fieldLabel: 'Stop time',
- name: 'stoptime',
- increment: 10,
- format: 'H:i'
- }), new Ext.form.ComboBox({
- store: tvheadend.dvrprio,
- value: "normal",
- triggerAction: 'all',
- mode: 'local',
- fieldLabel: 'Priority',
- valueField: 'identifier',
- displayField: 'name',
- name: 'pri'
- }), {
- allowBlank: false,
- fieldLabel: 'Title',
- name: 'title'
- }, new Ext.form.ComboBox({
- store: tvheadend.configNames,
- triggerAction: 'all',
- mode: 'local',
- fieldLabel: 'DVR Configuration',
- valueField: 'identifier',
- displayField: 'name',
- name: 'config_name',
- emptyText: '(default)',
- value: '',
- editable: false
- })],
- buttons: [{
- text: 'Create',
- handler: createRecording
- }]
- });
-
- win = new Ext.Window({
- title: 'Add single recording',
- layout: 'fit',
- width: 500,
- height: 300,
- plain: true,
- items: panel
- });
- win.show();
- new Ext.form.ComboBox({
- store: tvheadend.configNames,
- triggerAction: 'all',
- mode: 'local',
- fieldLabel: 'DVR Configuration',
- valueField: 'identifier',
- displayField: 'name',
- name: 'config_name',
- emptyText: '(default)',
- value: '',
- editable: false
- });
- }
- ;
-
- /* Create combobox to allow user to select page size for upcoming/completed/failed recordings */
-
- var itemPageCombo = new Ext.form.ComboBox({
- name : 'itemsperpage',
- width: 50,
- mode : 'local',
- store: new Ext.data.ArrayStore({
- fields: ['perpage','value'],
- data : [['10',10],['20',20],['30',30],['40',40],['50',50],['75',75],['100',100],['All',9999999999]]
- }),
- value : '20',
- listWidth : 40,
- triggerAction : 'all',
- displayField : 'perpage',
- valueField : 'value',
- editable : true,
- forceSelection : true,
- listeners : {
- scope: this,
- 'select' : function(combo, record) {
- bbar.pageSize = parseInt(record.get('value'), 10);
- bbar.doLoad(bbar.cursor);
- }
- }
- });
-
- /* Bottom toolbar to include default previous/goto-page/next and refresh buttons, also number-of-items combobox */
-
- var bbar = new Ext.PagingToolbar({
- store : dvrStore,
- displayInfo : true,
- items : ['-','Recordings per page: ',itemPageCombo],
- displayMsg : 'Programs {0} - {1} of {2}',
- emptyMsg : "No programs to display"
- });
-
- function abortEntry(btn) {
- if (btn !== 'yes')
- return;
-
- var selectedKeys = panel.selModel.selections.keys;
-
- // Delete each entry one by one since the API doesn't support deleting
- // multiple
- for (var i = 0; i < selectedKeys.length; i++) {
- var recordingId = selectedKeys[i];
-
- Ext.Ajax.request({
- url: 'dvr',
- params: {
- entryId: recordingId,
- op: 'cancelEntry'
- },
- failure: function(response, options) {
- Ext.MessageBox.alert('Server Error', 'Unable to cancel recording');
- }
- });
- }
- };
-
- function deleteEntry(btn) {
- if (btn !== 'yes')
- return;
-
- var selectedKeys = panel.selModel.selections.keys;
-
- // Delete each entry one by one since the API doesn't support deleting
- // multiple
- for (var i = 0; i < selectedKeys.length; i++) {
- var recordingId = selectedKeys[i];
-
- Ext.Ajax.request({
- url: 'dvr',
- params: {
- entryId: recordingId,
- op: 'deleteEntry'
- },
- success: function(response, options) {
-
- },
- failure: function(response, options) {
- Ext.MessageBox.alert('Server Error', 'Unable to delete recording');
- }
- });
- }
- };
-
- function downloadSelected() {
- var selectedKey = panel.selModel.selections.keys[0];
- var entry = dvrStore.getById(selectedKey);
-
- window.location = entry.data.url;
- }
-
- function abortSelected() {
- Ext.MessageBox.confirm('Message',
- 'Do you really want to abort/unschedule the selection?', abortEntry);
- };
-
- function deleteSelected() {
- Ext.MessageBox.confirm('Message',
- 'Do you really want to delete the selection?', deleteEntry);
- };
-
- var abortButton = new Ext.Toolbar.Button({
- tooltip: 'Abort or unschedule one or more selected rows',
- iconCls: 'remove',
- text: 'Abort/unschedule selected',
- handler: abortSelected,
- disabled: true
- });
-
- var downloadButton = new Ext.Toolbar.Button({
- tooltip: 'Download the selected recording',
- iconCls: 'save',
- text: 'Download',
- handler: downloadSelected,
- disabled: true
- });
-
- var deleteButton = new Ext.Toolbar.Button({
- tooltip: 'Delete one or more selected rows',
- iconCls: 'remove',
- text: 'Delete selected',
- handler: deleteSelected,
- disabled: true
- });
-
- // Make multiple rows selectable
- var selModel = new Ext.grid.RowSelectionModel({
- singleSelect: false
- });
-
- // Enable/disable some buttons when nothing is selected
- selModel.on('selectionchange', function(self) {
- if (self.getCount() > 0) {
- deleteButton.enable();
- abortButton.enable();
-
- // It only makes sense to download one item at a time
- if (self.getCount() === 1)
- downloadButton.enable();
- else
- downloadButton.disable();
- }
- else {
- downloadButton.disable();
- deleteButton.disable();
- abortButton.disable();
- }
- });
-
- // Define which panel buttons should be visible
- var panelButtons = [];
-
- // Add the "Add entry" and "Abort" buttons only to "Upcoming recordings"
- if (iconCls === 'clock') {
- panelButtons.push([
- {
- tooltip: 'Schedule a new recording session on the server.',
- iconCls: 'add',
- text: 'Add entry',
- handler: addEntry
+/**
+ *
+ */
+tvheadend.dvr_upcoming = function(panel, index) {
+
+ var actions = tvheadend.dvrRowActions();
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/dvr/entry',
+ gridURL: 'api/dvr/entry/grid_upcoming',
+ comet: 'dvrentry',
+ titleS: 'Upcoming Recording',
+ titleP: 'Upcoming Recordings',
+ iconCls: 'clock',
+ tabIndex: index,
+ add: {
+ url: 'api/dvr/entry',
+ params: {
+ list: 'disp_title,start,start_extra,stop,stop_extra,' +
+ 'channel,config_name',
},
- abortButton
- ]);
- }
- // Add the "Download" and "Delete" button to the others
- else {
- panelButtons.push(downloadButton);
- panelButtons.push(deleteButton);
- }
-
- // Add the help button to all panels
- panelButtons.push([
- '->',
- {
- text: 'Help',
- handler: function() {
- new tvheadend.help('Digital Video Recorder', 'dvrlog.html');
- }
- }
- ]);
-
- var panel = new Ext.grid.GridPanel({
- stateful: true,
- stateId: dvrStore.url,
- loadMask: true,
- stripeRows: true,
- disableSelection: false,
- title: title,
- iconCls: iconCls,
- store: dvrStore,
- selModel: selModel,
- cm: dvrCm,
+ create: { }
+ },
+ del: true,
+ list: 'disp_title,episode,pri,start_real,stop_real,' +
+ 'duration,channelname,creator,config_name,' +
+ 'sched_status',
+ sort: {
+ field: 'start',
+ direction: 'DESC'
+ },
plugins: [actions],
- viewConfig: {
- forceFit: true
+ lcol: [actions],
+ help: function() {
+ new tvheadend.help('DVR', 'config_dvr.html');
},
- tbar: panelButtons,
- bbar: bbar
});
return panel;
/**
*
*/
-tvheadend.autoreceditor = function() {
- var fm = Ext.form;
-
- var cm = new Ext.grid.ColumnModel({
- defaultSortable: true,
- columns:
- [
- {
- header: 'Enabled',
- dataIndex: 'enabled',
- width: 30,
- xtype: 'checkcolumn'
- },
- {
- header: "Title (Regexp)",
- dataIndex: 'title',
- editor: new fm.TextField({
- allowBlank: true
- })
- },
- {
- header: "Channel",
- dataIndex: 'channel',
- editor: new Ext.form.ComboBox({
- loadingText: 'Loading...',
- displayField: 'val',
- valueField: 'key',
- store: tvheadend.channels,
- mode: 'local',
- editable: true,
- forceSelection: true,
- typeAhead: true,
- triggerAction: 'all',
- emptyText: 'Only include channel...'
- }),
- renderer: function(v) {
- return tvheadend.channelLookupName(v);
- },
- },
- {
- header: "SeriesLink",
- dataIndex: 'serieslink',
- renderer: function(v) {
- return v ? 'yes' : 'no';
- }
- },
- {
- header: "Channel tag",
- dataIndex: 'tag',
- editor: new Ext.form.ComboBox({
- displayField: 'val',
- store: tvheadend.channelTags,
- mode: 'local',
- editable: true,
- forceSelection: true,
- typeAhead: true,
- triggerAction: 'all',
- emptyText: 'Only include tag...'
- })
- },
- {
- header: "Genre",
- dataIndex: 'contenttype',
- renderer: function(v) {
- return tvheadend.contentGroupLookupName(v);
- },
- editor: new Ext.form.ComboBox({
- valueField: 'code',
- displayField: 'name',
- store: tvheadend.ContentGroupStore,
- mode: 'local',
- editable: true,
- forceSelection: true,
- typeAhead: true,
- triggerAction: 'all',
- emptyText: 'Only include content...'
- })
- },
- {
- header: "Duration",
- dataIndex: 'minduration',
- renderer: function(v) {
- return tvheadend.durationLookupRange(v);
- },
- editor: durationCombo = new Ext.form.ComboBox({
- store: tvheadend.DurationStore,
- mode: 'local',
- valueField: 'minvalue',
- displayField: 'label',
- editable: true,
- forceSelection: true,
- typeAhead: true,
- triggerAction: 'all',
- id: 'minfield'
- })
- },
- {
- header: "Weekdays",
- dataIndex: 'weekdays',
- renderer: function(value, metadata, record, row, col, store) {
- if (value.split)
- value = value.split(',');
- if (value.length === 7)
- return 'All days';
- if (value.length === 0 || value[0] === "")
- return 'No days';
- ret = [];
- tags = value;
- for (var i = 0; i < tags.length; i++) {
- var tag = tvheadend.weekdays.getById(tags[i]);
- if (typeof tag !== 'undefined')
- ret.push(tag.data.name);
- }
- return ret.join(', ');
- },
- editor: new Ext.ux.form.LovCombo({
- store: tvheadend.weekdays,
- mode: 'local',
- valueField: 'identifier',
- displayField: 'name'
- })
- }, {
- header: "Starting Around",
- dataIndex: 'approx_time',
- renderer: function(value, metadata, record, row, col, store) {
- if (typeof value === 'string')
- return value;
-
- if (value === 0)
- return '';
-
- var hours = Math.floor(value / 60);
- var mins = value % 60;
- var dt = new Date();
- dt.setHours(hours);
- dt.setMinutes(mins);
- return dt.format('H:i');
- },
- editor: new Ext.form.TimeField({
- allowBlank: true,
- increment: 10,
- format: 'H:i'
- })
- }, {
- header: "Priority",
- dataIndex: 'pri',
- width: 100,
- renderer: function(value, metadata, record, row, col, store) {
- return tvheadend.dvrprio.getById(value).data.name;
- },
- editor: new fm.ComboBox({
- store: tvheadend.dvrprio,
- triggerAction: 'all',
- mode: 'local',
- valueField: 'identifier',
- displayField: 'name'
- })
- }, {
- header: "DVR Configuration",
- dataIndex: 'config_name',
- renderer: function(value, metadata, record, row, col, store) {
- if (!value) {
- return '<span class="tvh-grid-unset">(default)</span>';
- }
- else {
- return value;
- }
- },
- editor: new Ext.form.ComboBox({
- store: tvheadend.configNames,
- triggerAction: 'all',
- mode: 'local',
- valueField: 'identifier',
- displayField: 'name',
- name: 'config_name',
- emptyText: '(default)',
- editable: false
- })
- }, {
- header: "Created by",
- dataIndex: 'creator',
- editor: new fm.TextField({
- allowBlank: false
- })
- }, {
- header: "Comment",
- dataIndex: 'comment',
- editor: new fm.TextField({
- allowBlank: false
- })
- }]});
-
- tvheadend.autorecStore.on('update', function (store, record, operation) {
- if (operation == 'edit') {
- if (record.isModified('minduration')) {
- if (record.data.minduration == "")
- record.set('maxduration',"");
- else {
- var index = tvheadend.DurationStore.find('minvalue', record.data.minduration);
-
- if (index !== -1)
- record.set('maxduration', tvheadend.DurationStore.getById(index).data.maxvalue);
+tvheadend.dvr_finished = function(panel, index) {
+
+ var actions = tvheadend.dvrRowActions();
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/dvr/entry',
+ gridURL: 'api/dvr/entry/grid_finished',
+ readonly: true,
+ comet: 'dvrentry',
+ titleS: 'Finished Recording',
+ titleP: 'Finished Recordings',
+ iconCls: 'television',
+ tabIndex: index,
+ del: true,
+ list: 'disp_title,episode,start_real,stop_real,' +
+ 'duration,filesize,channelname,creator,' +
+ 'sched_status',
+ sort: {
+ field: 'start',
+ direction: 'DESC'
+ },
+ plugins: [actions],
+ lcol: [
+ actions,
+ {
+ width: 40,
+ header: "Play",
+ renderer: function(v, o, r) {
+ var title = r.data['title'];
+ if (r.data['episode'])
+ title += ' / ' + r.data['episode'];
+ return '<a href="play/dvrfile/' + r.data['id'] +
+ '?title=' + encodeURIComponent(title) + '">Play</a>';
}
- }
-
- if (record.isModified('channel') && record.data.channel == -1)
- record.set('channel',"");
-
- if (record.isModified('tag') && record.data.tag == '(Clear filter)')
- record.set('tag',"");
-
- if (record.isModified('contenttype') && record.data.contenttype == -1)
- record.set('contenttype',"");
- }
+ }],
+ help: function() {
+ new tvheadend.help('DVR', 'config_dvr.html');
+ },
});
- return new tvheadend.tableEditor('Automatic Recorder', 'autorec', cm,
- tvheadend.autorecRecord, [], tvheadend.autorecStore,
- 'autorec.html', 'wand');
+ return panel;
};
/**
*
*/
-tvheadend.dvr = function() {
-
- function datastoreBuilder(url) {
- return new Ext.data.JsonStore({
- root: 'entries',
- totalProperty: 'totalCount',
- fields: [{
- name: 'id'
- }, {
- name: 'channel'
- }, {
- name: 'title'
- }, {
- name: 'episode'
- }, {
- name: 'pri'
- }, {
- name: 'description'
- }, {
- name: 'chicon'
- }, {
- name: 'start',
- type: 'date',
- dateFormat: 'U' /* unix time */
- }, {
- name: 'end',
- type: 'date',
- dateFormat: 'U' /* unix time */
- }, {
- name: 'config_name'
- }, {
- name: 'status'
- }, {
- name: 'schedstate'
- }, {
- name: 'error'
- }, {
- name: 'creator'
- }, {
- name: 'duration'
- }, {
- name: 'filesize'
- }, {
- name: 'url'
- }],
- url: url,
- autoLoad: true,
- id: 'id'
- });
- }
- tvheadend.dvrStoreUpcoming = datastoreBuilder('dvrlist_upcoming');
- tvheadend.dvrStoreFinished = datastoreBuilder('dvrlist_finished');
- tvheadend.dvrStoreFailed = datastoreBuilder('dvrlist_failed');
- tvheadend.dvrStores = [tvheadend.dvrStoreUpcoming,
- tvheadend.dvrStoreFinished,
- tvheadend.dvrStoreFailed];
-
-
- function reloadStores() {
- for (var i = 0; i < tvheadend.dvrStores.length; i++) {
- tvheadend.dvrStores[i].reload();
- }
- }
-
- tvheadend.comet.on('dvrdb', function(m) {
- reloadStores();
- });
-
- tvheadend.autorecRecord = Ext.data.Record.create(['enabled', 'title',
- 'serieslink', 'channel', 'tag', 'creator', 'contenttype', 'comment',
- 'minduration', 'maxduration',
- 'weekdays', 'pri', 'approx_time', 'config_name']);
-
- tvheadend.autorecStore = new Ext.data.JsonStore({
- root: 'entries',
- fields: tvheadend.autorecRecord,
- url: "tablemgr",
- autoLoad: true,
- id: 'id',
- baseParams: {
- table: "autorec",
- op: "get"
- }
- });
-
- tvheadend.comet.on('autorec', function(m) {
- if (m.reload != null)
- tvheadend.autorecStore.reload();
+tvheadend.dvr_failed = function(panel, index) {
+
+ var actions = tvheadend.dvrRowActions();
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/dvr/entry',
+ gridURL: 'api/dvr/entry/grid_failed',
+ readonly: true,
+ comet: 'dvrentry',
+ titleS: 'Failed Recording',
+ titleP: 'Failed Recordings',
+ iconCls: 'exclamation',
+ tabIndex: index,
+ del: true,
+ list: 'disp_title,episode,start_real,stop_real,' +
+ 'duration,channelname,creator,' +
+ 'status,sched_status',
+ sort: {
+ field: 'start',
+ direction: 'DESC'
+ },
+ plugins: [actions],
+ lcol: [actions],
+ help: function() {
+ new tvheadend.help('DVR', 'config_dvr.html');
+ },
});
- var panel = new Ext.TabPanel({
- activeTab: 0,
- autoScroll: true,
- title: 'Digital Video Recorder',
- iconCls: 'drive',
- items: [
- new tvheadend.dvrschedule('Upcoming recordings', 'clock', tvheadend.dvrStoreUpcoming),
- new tvheadend.dvrschedule('Finished recordings', 'television', tvheadend.dvrStoreFinished),
- new tvheadend.dvrschedule('Failed recordings', 'exclamation', tvheadend.dvrStoreFailed),
- new tvheadend.autoreceditor
- ]
- });
return panel;
};
/**
- * Configuration panel (located under configuration)
+ *
*/
-tvheadend.dvrsettings = function() {
-
- var confreader = new Ext.data.JsonReader({
- root: 'dvrSettings'
- }, ['storage', 'filePermissions', 'dirPermissions', 'postproc', 'retention', 'dayDirs', 'channelDirs',
- 'channelInTitle', 'container', 'cache', 'charset', 'dateInTitle', 'timeInTitle',
- 'preExtraTime', 'postExtraTime', 'whitespaceInTitle', 'titleDirs',
- 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', 'subtitleInTitle',
- 'episodeBeforeDate', 'rewritePAT', 'rewritePMT', 'episodeDuplicateDetection']);
-
- var confcombo = new Ext.form.ComboBox({
- store: tvheadend.configNames,
- triggerAction: 'all',
- mode: 'local',
- displayField: 'name',
- name: 'config_name',
- emptyText: '(default)',
- value: '',
- editable: true
- });
-
- var delButton = new Ext.Toolbar.Button({
- tooltip: 'Delete named configuration',
- iconCls: 'remove',
- text: "Delete configuration",
- handler: deleteConfiguration,
- disabled: true
- });
-
- /* Config panel variables */
-
- /* DVR Behaviour */
-
- var recordingContainer = new Ext.form.ComboBox({
- store: tvheadend.containers,
- fieldLabel: 'Media container',
- triggerAction: 'all',
- displayField: 'description',
- valueField: 'name',
- editable: false,
- width: 350,
- hiddenName: 'container'
- });
-
- var cacheScheme = new Ext.form.ComboBox({
- store: tvheadend.caches,
- fieldLabel: 'Cache scheme',
- triggerAction: 'all',
- displayField: 'description',
- valueField: 'index',
- editable: false,
- width: 350,
- hiddenName: 'cache'
- });
-
- var logRetention = new Ext.form.NumberField({
- allowNegative: false,
- allowDecimals: false,
- minValue: 1,
- fieldLabel: 'DVR Log retention time (days)',
- name: 'retention'
- });
-
- var timeBefore = new Ext.form.NumberField({
- allowDecimals: false,
- fieldLabel: 'Extra time before recordings (minutes)',
- name: 'preExtraTime'
- });
-
- var timeAfter = new Ext.form.NumberField({
- allowDecimals: false,
- fieldLabel: 'Extra time after recordings (minutes)',
- name: 'postExtraTime'
- });
-
- var postProcessing = new Ext.form.TextField({
- width: 350,
- fieldLabel: 'Post-processor command',
- name: 'postproc'
- });
-
- /* Recording File Options */
-
- var recordingPath = new Ext.form.TextField({
- width: 350,
- fieldLabel: 'Recording system path',
- name: 'storage'
- });
-
- /* NB: recordingPermissions is defined as a TextField for validation purposes (leading zeros), but is ultimately a number */
-
- var recordingPermissions = new Ext.form.TextField({
- regex: /^[0][0-7]{3}$/,
- maskRe: /[0-7]/,
- width: 125,
- allowBlank: false,
- blankText: 'You must provide a value - use octal chmod notation, e.g. 0664',
- fieldLabel: 'File permissions (octal, e.g. 0664)',
- name: 'filePermissions'
- });
-
- var charset = new Ext.form.ComboBox({
- store: tvheadend.charsets,
- fieldLabel: 'Filename charset',
- triggerAction: 'all',
- displayField: 'val',
- valueField: 'key',
- editable: false,
- width: 200,
- hiddenName: 'charset'
- });
-
- /* TO DO - Add 'override user umask?' option, then trigger fchmod in mkmux.c, muxer_pass.c after file created */
-
- var PATrewrite = new Ext.form.Checkbox({
- fieldLabel: 'Rewrite PAT in passthrough mode',
- name: 'rewritePAT'
- });
-
- var PMTrewrite = new Ext.form.Checkbox({
- fieldLabel: 'Rewrite PMT in passthrough mode',
- name: 'rewritePMT'
- });
-
- var tagMetadata = new Ext.form.Checkbox({
- fieldLabel: 'Tag files with metadata',
- name: 'tagFiles'
- });
-
- var skipCommercials = new Ext.form.Checkbox({
- fieldLabel: 'Skip commercials',
- name: 'commSkip'
- });
-
- var episodeDuplicateDetection = new Ext.form.Checkbox({
- fieldLabel: 'Episode Duplicate Detect',
- name: 'episodeDuplicateDetection'
- });
-
- /* Subdirectories and filename handling */
-
- /* NB: directoryPermissions is defined as a TextField for validation purposes (leading zeros), but is ultimately a number */
-
- var directoryPermissions = new Ext.form.TextField({
- regex: /^[0][0-7]{3}$/,
- maskRe: /[0-7]/,
- width: 125,
- allowBlank: false,
- blankText: 'You must provide a value - use octal chmod notation, e.g. 0775',
- fieldLabel: 'Directory permissions (octal, e.g. 0775)',
- name: 'dirPermissions'
- });
-
- /* TO DO - Add 'override user umask?' option, then trigger fchmod in utils.c after directory created */
-
- var dirsPerDay = new Ext.form.Checkbox({
- fieldLabel: 'Make subdirectories per day',
- name: 'dayDirs'
- });
-
- var dirsPerChannel = new Ext.form.Checkbox({
- fieldLabel: 'Make subdirectories per channel',
- name: 'channelDirs'
- });
-
- var dirsPerTitle = new Ext.form.Checkbox({
- fieldLabel: 'Make subdirectories per title',
- name: 'titleDirs'
- });
-
- var incChannelInTitle = new Ext.form.Checkbox({
- fieldLabel: 'Include channel name in filename',
- name: 'channelInTitle'
- });
-
- var incDateInTitle = new Ext.form.Checkbox({
- fieldLabel: 'Include date in filename',
- name: 'dateInTitle'
- });
-
- var incTimeInTitle = new Ext.form.Checkbox({
- fieldLabel: 'Include time in filename',
- name: 'timeInTitle'
- });
-
- var incEpisodeInTitle = new Ext.form.Checkbox({
- fieldLabel: 'Include episode in filename',
- name: 'episodeInTitle'
- });
-
- var incSubtitleInTitle = new Ext.form.Checkbox({
- fieldLabel: 'Include subtitle in filename',
- name: 'subtitleInTitle'
- });
-
- var episodeFirst = new Ext.form.Checkbox({
- fieldLabel: 'Put episode in filename before date and time',
- name: 'episodeBeforeDate'
- });
-
- var stripUnsafeChars = new Ext.form.Checkbox({
- fieldLabel: 'Remove all unsafe characters from filename',
- name: 'cleanTitle'
- });
-
- var stripWhitespace = new Ext.form.Checkbox({
- fieldLabel: 'Replace whitespace in title with \'-\'',
- name: 'whitespaceInTitle'
- });
-
- /* Sub-Panel - DVR behaviour */
-
- var DVRBehaviour = new Ext.form.FieldSet({
- title: 'DVR Behaviour',
- width: 700,
- autoHeight: true,
- collapsible: true,
- animCollapse: true,
- items: [recordingContainer, cacheScheme, logRetention, timeBefore, timeAfter, postProcessing]
- });
-
- /* Sub-Panel - File Output */
-
- var FileOutputPanel = new Ext.form.FieldSet({
- title: 'Recording File Options',
- width: 700,
- autoHeight: true,
- collapsible: true,
- animCollapse: true,
- items: [recordingPath, recordingPermissions, charset, PATrewrite, PMTrewrite, tagMetadata, skipCommercials, episodeDuplicateDetection]
- });
-
- /* Sub-Panel - Directory operations */
-
- var DirHandlingPanel = new Ext.form.FieldSet({
- title: 'Subdirectory Options',
- width: 700,
- autoHeight: true,
- collapsible: true,
- animCollapse: true,
- items: [directoryPermissions, dirsPerDay, dirsPerChannel, dirsPerTitle]
+tvheadend.dvr_settings = function(panel, index) {
+ tvheadend.idnode_form_grid(panel, {
+ url: 'api/dvr/config',
+ clazz: 'dvrconfig',
+ comet: 'dvrconfig',
+ titleS: 'Digital Video Recorder Profile',
+ titleP: 'Digital Video Recorder Profiles',
+ titleC: 'Profile Name',
+ iconCls: 'drive',
+ tabIndex: index,
+ add: {
+ url: 'api/dvr/config',
+ create: { }
+ },
+ del: true,
+ sort: {
+ field: 'name',
+ direction: 'ASC'
+ },
+ help: function() {
+ new tvheadend.help('DVR', 'config_dvr.html');
+ },
});
- /* Sub-Panel - File operations - Break into two 4-item panels */
+ return panel;
- var FileHandlingPanelA = new Ext.form.FieldSet({
- width: 350,
- border: false,
- autoHeight: true,
- items : [incChannelInTitle, incDateInTitle, incTimeInTitle, incEpisodeInTitle]
- });
+}
- var FileHandlingPanelB = new Ext.form.FieldSet({
- width: 350,
- border: false,
- autoHeight: true,
- items : [incSubtitleInTitle, episodeFirst, stripUnsafeChars, stripWhitespace]
+/**
+ *
+ */
+tvheadend.autorec_editor = function(panel, index) {
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/dvr/autorec',
+ comet: 'dvrautorec',
+ titleS: 'DVR AutoRec Entry',
+ titleP: 'DVR AutoRec Entries',
+ iconCls: 'wand',
+ tabIndex: index,
+ add: {
+ url: 'api/dvr/autorec',
+ params: {
+ list: 'enable,title,channel,tag,content_type,minduration,' +
+ 'weekdays,approx_time,pri,config_name,comment',
+ },
+ create: { }
+ },
+ del: true,
+ list: 'enable,title,channel,tag,content_type,minduration,' +
+ 'weekdays,approx_time,pri,config_name,creator,comment',
+ sort: {
+ field: 'name',
+ direction: 'ASC'
+ },
+ help: function() {
+ new tvheadend.help('DVR', 'config_dvr.html');
+ },
});
- var FileHandlingPanel = new Ext.form.FieldSet({
- title: 'Filename Options',
- width: 700,
- autoHeight: true,
- collapsible: true,
- animCollapse : true,
- items : [{
- layout: 'column',
- border: false,
- items : [FileHandlingPanelA, FileHandlingPanelB]
- }]
- });
+ return panel;
- /* Main (form) panel */
+};
- var confpanel = new Ext.FormPanel({
+/**
+ *
+ */
+tvheadend.dvr = function() {
+ var panel = new Ext.TabPanel({
+ activeTab: 0,
+ autoScroll: true,
title: 'Digital Video Recorder',
iconCls: 'drive',
- border: false,
- bodyStyle: 'padding:15px',
- anchor: '100% 50%',
- labelAlign: 'right',
- labelWidth: 300,
- autoScroll: true,
- waitMsgTarget: true,
- reader: confreader,
- defaultType: 'textfield',
- layout: 'form',
- items: [DVRBehaviour, FileOutputPanel, DirHandlingPanel, FileHandlingPanel],
- tbar: [confcombo, {
- tooltip: 'Save changes made to dvr configuration below',
- iconCls: 'save',
- text: "Save configuration",
- handler: saveChanges
- }, delButton, '->', {
- text: 'Help',
- handler: function() {
- new tvheadend.help('DVR configuration', 'config_dvr.html');
- }
- }]
- });
-
- function loadConfig() {
- confpanel.getForm().load({
- url: 'dvr',
- params: {
- 'op': 'loadSettings',
- 'config_name': confcombo.getValue()
- },
- success: function(form, action) {
- confpanel.enable();
- }
- });
- }
-
- confcombo.on('select', function() {
- if (confcombo.getValue() === '')
- delButton.disable();
- else
- delButton.enable();
- loadConfig();
- });
-
- confpanel.on('render', function() {
- loadConfig();
+ items: [],
});
-
- function saveChanges() {
- var config_name = confcombo.getValue();
- confpanel.getForm().submit({
- url: 'dvr',
- params: {
- 'op': 'saveSettings',
- 'config_name': config_name
- },
- waitMsg: 'Saving Data...',
- success: function(form, action) {
- confcombo.setValue(config_name);
- confcombo.fireEvent('select');
- },
- failure: function(form, action) {
- Ext.Msg.alert('Save failed', action.result.errormsg);
- }
- });
- }
-
- function deleteConfiguration() {
- if (confcombo.getValue() !== "") {
- Ext.MessageBox.confirm('Message',
- 'Do you really want to delete DVR configuration \''
- + confcombo.getValue() + '\'?', deleteAction);
- }
- }
-
- function deleteAction(btn) {
- if (btn === 'yes') {
- confpanel.getForm().submit({
- url: 'dvr',
- params: {
- 'op': 'deleteSettings',
- 'config_name': confcombo.getValue()
- },
- waitMsg: 'Deleting Data...',
- success: function(form, action) {
- confcombo.setValue('');
- confcombo.fireEvent('select');
- },
- failure: function(form, action) {
- Ext.Msg.alert('Delete failed', action.result.errormsg);
- }
- });
- }
- }
-
- return confpanel;
-};
+ tvheadend.dvr_upcoming(panel, 0);
+ tvheadend.dvr_finished(panel, 1);
+ tvheadend.dvr_failed(panel, 2);
+ tvheadend.autorec_editor(panel, 3);
+ return panel;
+}
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 = "";
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>';
'?title=' + encodeURIComponent(title) + '">Play</a></div>';
}
+ 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: tvheadend.configNames,
+ store: store,
triggerAction: 'all',
mode: 'local',
- valueField: 'identifier',
- displayField: 'name',
+ valueField: 'key',
+ displayField: 'val',
name: 'config_name',
emptyText: '(default)',
value: '',
win.show();
function recordEvent() {
- record('recordEvent');
+ record('event')
}
function recordSeries() {
- record('recordSeries');
+ record('series');
}
function record(op) {
Ext.Ajax.request({
- url: 'dvr',
+ url: 'api/dvr/entry/create_by_' + op,
params: {
- eventId: event.id,
- op: op,
- config_name: confcombo.getValue()
+ event_id: event.id,
+ config_uuid: confcombo.getValue()
},
success: function(response, options) {
win.close();
}, {
name: 'agerating'
}, {
- name: 'contenttype'
+ name: 'content_type'
}, {
name: 'schedstate'
}, {
renderer: renderInt
}, {
width: 250,
- id: 'contenttype',
+ id: 'content_type',
header: "Content Type",
- dataIndex: 'contenttype',
+ dataIndex: 'content_type',
renderer: function(v) {
return tvheadend.contentGroupLookupName(v);
}
};
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("");
};
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();
});
: "<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?',
function createAutoRec2(params) {
/* Really do it */
- params.op = 'createAutoRec';
+ params.conf = {
+ enabled: 1,
+ comment: 'Created from EPG query',
+ };
+ if (params.title) params['title'] = params.title;
+ if (params.channel) params['channel'] = params.channel;
+ if (params.tag) params['tag'] = params.tag;
+ if (params.content_type) params['content_type'] = params.content_type;
+ if (params.minduration) params['minduration'] = params.minduration;
+ if (params.maxduration) params['maxduration'] = params.maxduration;
Ext.Ajax.request({
- url: 'dvr',
+ url: 'api/dvr/autorec/create',
params: params
});
}
*/
-
-
/*
* Ext JS Library 2.2
* Copyright(c) 2006-2008, Ext JS, LLC.
// 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;
+ }
+});
+
if (key in tvheadend.idnode_enum_stores)
return tvheadend.idnode_enum_stores[key];
- /* Build combobox */
+ /* Build store */
var st = new Ext.data.JsonStore({
root: conf.root || 'entries',
url: conf.url,
fields: conf.fields || ['key', 'val'],
id: conf.id || 'key',
autoLoad: true,
- sortInfo: {
+ listeners: conf.listeners || {},
+ sortInfo: conf.sort || {
field: 'val',
direction: 'ASC'
}
case 'int':
case 'u32':
case 'u16':
+ case 's64':
case 'dbl':
+ case 'time':
var data = null;
if (f.enum.length > 0 && f.enum[0] instanceof Object) {
data = f.enum;
this.wronce = conf.wronce;
this.hidden = conf.hidden || conf.advanced;
this.password = conf.password;
+ this.duration = conf.duration;
+ this.group = conf.group;
this.enum = conf.enum;
this.store = null;
if (this.enum)
var w = 300;
var ftype = 'string';
if (this.type === 'int' || this.type === 'u32' ||
- this.type === 'u16' || this.type === 'dbl') {
+ this.type === 'u16' || this.type === 's64' ||
+ this.type === 'dbl') {
ftype = 'numeric';
w = 80;
+ } else if (this.type === 'time') {
+ w = 120;
+ if (this.durations) {
+ ftype = 'numeric';
+ w = 80;
+ }
} else if (this.type === 'bool') {
ftype = 'boolean';
w = 60;
return function(v) {
return '<span class="tvh-grid-unset">********</span>';
}
-
+
+ if (this.type === 'time') {
+ if (this.duration)
+ return function(v) {
+ v = parseInt(v / 60); /* Nevermind the seconds */
+ if (v === 0 && v !== '0')
+ return "Not set";
+ var hours = parseInt(v / 60);
+ var min = parseInt(v % 60);
+ if (hours) {
+ if (min === 0)
+ return hours + ' hrs';
+ return hours + ' hrs, ' + min + ' min';
+ }
+ return min + ' min';
+ }
+ return function(v) {
+ var dt = new Date(v * 1000);
+ return dt.format('D j M H:i');
+ }
+ }
+
if (!this.store)
return null;
return function(v) {
if (st && st instanceof Ext.data.JsonStore) {
var t = [];
- var d;
- if (v.push)
- d = v;
- else
- d = [v];
+ var d = v.push ? v : [v];
for (var i = 0; i < d.length; i++) {
var r = st.find('key', d[i]);
if (r !== -1) {
disabled: d,
width: 300
};
-
+
/* ComboBox */
if (this.enum) {
cons = Ext.form.ComboBox;
c['store'] = this.store;
c['typeAhead'] = true;
c['forceSelection'] = false;
- c['triggerAction'] = 'all',
- c['emptyText'] = 'Select ' + this.text + ' ...';
+ c['triggerAction'] = 'all';
+ c['emptyText'] = 'Select ' + this.text + ' ...';
/* Single */
} else {
+
+ if (this.type == 'perm') {
+ c['regex'] = /^[0][0-7]{3}$/;
+ c['maskRe'] = /[0-7]/;
+ c['allowBlank'] = false;
+ c['blankText'] = 'You must provide a value - use octal chmod notation, e.g. 0664';
+ c['width'] = 125;
+ }
+
switch (this.type) {
case 'bool':
cons = Ext.form.Checkbox;
case 'int':
case 'u32':
case 'u16':
+ case 's32':
case 'dbl':
+ case 'time':
cons = Ext.form.NumberField;
break;
+ /* 'str' and 'perm' */
default:
cons = Ext.form.TextField;
break;
this.text = conf.caption || this.clazz;
this.props = conf.props;
this.order = [];
+ this.groups = conf.groups;
this.fields = [];
for (var i = 0; i < this.props.length; i++) {
this.fields.push(new tvheadend.IdNodeField(this.props[i]));
/* Singular */
switch (f.type) {
- case 'str':
- return new Ext.form.TextField({
- fieldLabel: f.caption,
- name: f.id,
- value: value,
- disabled: d,
- width: 300
- });
- break;
-
case 'bool':
return new Ext.ux.form.XCheckbox({
fieldLabel: f.caption,
checked: value,
disabled: d
});
- break;
+
+ case 'time':
+ if (!f.duration)
+ return new Ext.ux.form.TwinDateTimeField({
+ fieldLabel: f.caption,
+ name: f.id,
+ value: value,
+ disabled: d,
+ width: 300,
+ timeFormat: 'H:i:s',
+ timeConfig: {
+ altFormats: 'H:i:s',
+ allowBlank: true,
+ increment: 10,
+ },
+ dateFormat:'d.n.Y',
+ dateConfig: {
+ altFormats: 'Y-m-d|Y-n-d',
+ allowBlank: true,
+ }
+ });
+ /* fall thru!!! */
case 'int':
case 'u32':
case 'u16':
+ case 's64':
case 'dbl':
return new Ext.form.NumberField({
fieldLabel: f.caption,
disabled: d,
width: 300
});
- break;
+
+ case 'perm':
+ return new Ext.form.TextField({
+ fieldLabel: f.caption,
+ name: f.id,
+ value: value,
+ disabled: d,
+ width: 125,
+ regex: /^[0][0-7]{3}$/,
+ maskRe: /[0-7]/,
+ allowBlank: false,
+ blankText: 'You must provide a value - use octal chmod notation, e.g. 0664'
+ });
+
+
+ default:
+ return new Ext.form.TextField({
+ fieldLabel: f.caption,
+ name: f.id,
+ value: value,
+ disabled: d,
+ width: 300
+ });
+
}
- return null;
};
/*
* ID node editor form fields
*/
-tvheadend.idnode_editor_form = function(d, panel)
+tvheadend.idnode_editor_form = function(d, meta, panel, create)
{
var af = [];
var rf = [];
var df = [];
+ var groups = null;
/* Fields */
for (var i = 0; i < d.length; i++) {
- var f = tvheadend.idnode_editor_field(d[i]);
+ var p = d[i];
+ var f = tvheadend.idnode_editor_field(p, create);
if (!f)
continue;
- if (d[i].rdonly)
- rf.push(f);
- else if (d[i].advanced)
- af.push(f);
- else
- df.push(f);
- }
- if (df.length) {
- panel.add(new Ext.form.FieldSet({
- title: 'Basic Settings',
- autoHeight: true,
- autoWidth: true,
- collapsible: true,
- collapsed: false,
- items: df
- }));
+ if (p.group && meta.groups) {
+ if (!groups)
+ groups = {};
+ if (!(p.group in groups))
+ groups[p.group] = [f];
+ else
+ groups[p.group].push(f);
+ } else {
+ if (p.rdonly)
+ rf.push(f);
+ else if (p.advanced)
+ af.push(f);
+ else
+ df.push(f);
+ }
}
- if (af.length) {
- panel.add(new Ext.form.FieldSet({
- title: 'Advanced Settings',
- autoHeight: true,
- autoWidth: true,
- collapsible: true,
- collapsed: false, //true,
- items: af
- }));
+
+ function newFieldSet(conf) {
+ return new Ext.form.FieldSet({
+ title: conf.title || '',
+ layout: conf.layout || 'form',
+ border: conf.border || true,
+ style: conf.style || 'padding: 0 5px 5px 10px',
+ bodyStyle: conf.bodyStyle || 'padding-top: ' + (Ext.isIE ? '0' : '10px'),
+ autoHeight: true,
+ autoWidth: true,
+ collapsible: conf.nocollapse ? false : true,
+ collapsed: false,
+ items: conf.items
+ });
}
- if (rf.length) {
- panel.add(new Ext.form.FieldSet({
- title: 'Read-only Info',
- autoHeight: true,
- autoWidth: true,
- collapsible: true,
- collapsed: false, //true,
- items: rf
- }));
+
+ if (groups) {
+ var met = {};
+ for (var i = 0; i < meta.groups.length; i++)
+ met[meta.groups[i].number] = meta.groups[i];
+ var fs = {};
+ var cfs = {};
+ var mfs = {};
+ var rest = [];
+ for (var number in groups) {
+ if (!(number in met))
+ met[number] = null;
+ var m = met[number];
+ var columns = 0;
+ for (var k in met)
+ if (met[k].parent == m.number)
+ if (columns < met[k].column)
+ columns = met[k].column;
+ met[number].columns = columns;
+ if (columns) {
+ var p = newFieldSet({ title: m.name || "Settings", layout: 'column', border: false });
+ cfs[number] = newFieldSet({ nocollapse: true });
+ p.add(cfs[number]);
+ fs[number] = p;
+ mfs[number] = p;
+ }
+ }
+ for (var number in groups) {
+ var m = met[number];
+ if (number in fs) continue;
+ var parent = m.parent;
+ var p = null;
+ if (parent && !met[parent].columns)
+ parent = null;
+ if (!m.columns) {
+ if (parent) {
+ p = newFieldSet({ nocollapse: true });
+ fs[parent].add(p);
+ } else {
+ p = newFieldSet({ title: m.name });
+ mfs[number] = p;
+ }
+ cfs[number] = p;
+ }
+ }
+ for (var number in groups) {
+ var g = groups[number];
+ for (var i = 0; i < g.length; i++)
+ cfs[number].add(g[i]);
+ if (number in mfs)
+ panel.add(mfs[number]);
+ }
}
+ if (df.length)
+ panel.add(newFieldSet({ title: "Basic Settings", items: df }));
+ if (af.length)
+ panel.add(newFieldSet({ title: "Advanced Settings", items: af }));
+ if (rf.length)
+ panel.add(newFieldSet({ title: "Read-only Info", items: rf }));
panel.doLayout();
};
var buttons = [];
/* Buttons */
- var saveBtn = new Ext.Button({
- text: 'Save',
- handler: function() {
- var node = panel.getForm().getFieldValues();
- node.uuid = item.uuid;
- Ext.Ajax.request({
- url: 'api/idnode/save',
- params: {
- node: Ext.encode(node)
- },
- success: function(d) {
- if (conf.win)
- conf.win.hide();
- }
+ if (!conf.noButtons) {
+ var saveBtn = new Ext.Button({
+ text: 'Save',
+ handler: function() {
+ var node = panel.getForm().getFieldValues();
+ node.uuid = item.uuid;
+ tvheadend.Ajax({
+ url: 'api/idnode/save',
+ params: {
+ node: Ext.encode(node)
+ },
+ success: function(d) {
+ if (conf.win)
+ conf.win.hide();
+ }
+ });
+ }
+ });
+ buttons.push(saveBtn);
+
+ if (conf.help) {
+ var helpBtn = new Ext.Button({
+ text: 'Help',
+ handler: conf.help
});
+ buttons.push(helpBtn);
}
- });
- buttons.push(saveBtn);
-
- if (conf.help) {
- var helpBtn = new Ext.Button({
- text: 'Help',
- handler: conf.help
- });
- buttons.push(helpBtn);
}
- panel = new Ext.FormPanel({
+ panel = new Ext.form.FormPanel({
title: conf.title || null,
frame: true,
- border: true,
+ border: conf.inTabPanel ? false : true,
bodyStyle: 'padding: 5px',
labelAlign: 'left',
- labelWidth: 200,
+ labelWidth: conf.labelWidth || 200,
autoWidth: true,
autoHeight: !conf.fixedHeight,
width: 600,
//defaults: {width: 330},
defaultType: 'textfield',
buttonAlign: 'left',
- buttons: buttons
+ autoScroll: true,
+ buttons: buttons,
});
- tvheadend.idnode_editor_form(item.props || item.params, panel);
+ tvheadend.idnode_editor_form(item.props || item.params, item.meta, panel, false);
return panel;
};
/*
* IDnode creation dialog
*/
-tvheadend.idnode_create = function(conf)
+tvheadend.idnode_create = function(conf, onlyDefault)
{
var puuid = null;
var panel = null;
text: 'Create',
hidden: true,
handler: function() {
- params = conf.create.params || {};
+ var params = conf.create.params || {};
if (puuid)
params['uuid'] = puuid;
if (pclass)
params['class'] = pclass;
params['conf'] = Ext.encode(panel.getForm().getFieldValues());
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: conf.create.url || conf.url + '/create',
params: params,
success: function(d) {
pclass = r.get(conf.select.valueField);
win.setTitle('Add ' + s.lastSelectionText);
panel.remove(s);
- tvheadend.idnode_editor_form(d, panel);
+ tvheadend.idnode_editor_form(d, null, panel, true);
saveBtn.setVisible(true);
}
}
select = function(s, n, o) {
params = conf.select.clazz.params || {};
params['uuid'] = puuid = n.id;
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: conf.select.clazz.url || conf.select.url || conf.url,
+ params: params,
success: function(d) {
panel.remove(s);
d = json_decode(d);
- tvheadend.idnode_editor_form(d.props, panel);
+ tvheadend.idnode_editor_form(d.props, d, panel, true);
saveBtn.setVisible(true);
- },
- params: params
+ }
});
};
}
panel.add(combo);
win.show();
} else {
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: conf.url + '/class',
params: conf.params,
success: function(d) {
d = json_decode(d);
- tvheadend.idnode_editor_form(d.props, panel);
+ tvheadend.idnode_editor_form(d.props, d, panel, true);
saveBtn.setVisible(true);
- win.show();
+ if (onlyDefault) {
+ saveBtn.handler();
+ panel.destroy();
+ } else
+ win.show();
}
});
}
var downBtn = null;
var editBtn = null;
+ /* Some copies */
+ if (conf.add && !conf.add.titleS && conf.titleS)
+ conf.add.titleS = conf.titleS;
+
/* Model */
var idnode = new tvheadend.IdNode(d);
for (var i = 0; i < idnode.length(); i++) {
/* Store */
var store = new Ext.data.JsonStore({
root: 'entries',
- url: conf.url + '/grid',
+ url: conf.gridURL || (conf.url + '/grid'),
autoLoad: true,
id: 'uuid',
totalProperty: 'total',
saveBtn.setDisabled(d);
});
select.on('selectionchange', function(s) {
+ var count = s.getCount();
if (delBtn)
- delBtn.setDisabled(s.getCount() === 0);
+ delBtn.setDisabled(count === 0);
if (upBtn) {
- upBtn.setDisabled(s.getCount() === 0);
- downBtn.setDisabled(s.getCount() === 0);
+ upBtn.setDisabled(count === 0);
+ downBtn.setDisabled(count === 0);
}
- editBtn.setDisabled(s.getCount() !== 1);
+ if (editBtn)
+ editBtn.setDisabled(count !== 1);
if (conf.selected)
conf.selected(s);
});
/* Top bar */
- saveBtn = new Ext.Toolbar.Button({
- tooltip: 'Save pending changes (marked with red border)',
- iconCls: 'save',
- text: 'Save',
- disabled: true,
- handler: function() {
- 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].uuid = mr[x].id;
- }
- Ext.Ajax.request({
- url: 'api/idnode/save',
- params: {
- node: Ext.encode(out)
- },
- success: function(d)
- {
- if (!auto.getValue())
- store.reload();
+ if (!conf.readonly) {
+ saveBtn = new Ext.Toolbar.Button({
+ tooltip: 'Save pending changes (marked with red border)',
+ iconCls: 'save',
+ text: 'Save',
+ disabled: true,
+ handler: function() {
+ 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].uuid = mr[x].id;
}
- });
- }
- });
- buttons.push(saveBtn);
- undoBtn = new Ext.Toolbar.Button({
- tooltip: 'Revert pending changes (marked with red border)',
- iconCls: 'undo',
- text: 'Undo',
- disabled: true,
- handler: function() {
- store.rejectChanges();
- }
- });
- buttons.push(undoBtn);
- buttons.push('-');
+ tvheadend.Ajax({
+ url: 'api/idnode/save',
+ params: {
+ node: Ext.encode(out)
+ },
+ success: function(d)
+ {
+ if (!auto.getValue())
+ store.reload();
+ }
+ });
+ }
+ });
+ buttons.push(saveBtn);
+ undoBtn = new Ext.Toolbar.Button({
+ tooltip: 'Revert pending changes (marked with red border)',
+ iconCls: 'undo',
+ text: 'Undo',
+ disabled: true,
+ handler: function() {
+ store.rejectChanges();
+ }
+ });
+ buttons.push(undoBtn);
+ }
if (conf.add) {
+ if (buttons.length > 0)
+ buttons.push('-');
addBtn = new Ext.Toolbar.Button({
tooltip: 'Add a new entry',
iconCls: 'add',
buttons.push(addBtn);
}
if (conf.del) {
+ if (!conf.add && buttons.length > 0)
+ buttons.push('-');
delBtn = new Ext.Toolbar.Button({
tooltip: 'Delete selected entries',
iconCls: 'remove',
var uuids = [];
for (var i = 0; i < r.length; i++)
uuids.push(r[i].id);
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: 'api/idnode/delete',
params: {
uuid: Ext.encode(uuids)
var uuids = [];
for (var i = 0; i < r.length; i++)
uuids.push(r[i].id);
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: 'api/idnode/moveup',
params: {
uuid: Ext.encode(uuids)
var uuids = [];
for (var i = 0; i < r.length; i++)
uuids.push(r[i].id);
- Ext.Ajax.request({
+ tvheadend.Ajax({
url: 'api/idnode/movedown',
params: {
uuid: Ext.encode(uuids)
});
buttons.push(downBtn);
}
- if (conf.add || conf.del || conf.move)
- buttons.push('-');
- editBtn = new Ext.Toolbar.Button({
- tooltip: 'Edit selected entry',
- iconCls: 'edit',
- text: 'Edit',
- disabled: true,
- handler: function() {
- var r = select.getSelected();
- if (r) {
- if (conf.edittree) {
- var p = tvheadend.idnode_tree({
- url: 'api/idnode/tree',
- params: {
- root: r.id
- }
- });
- p.setSize(800, 600);
- var w = new Ext.Window({
- title: 'Edit ' + conf.titleS,
- layout: 'fit',
- autoWidth: true,
- autoHeight: true,
- plain: true,
- items: p
- });
- w.show();
- } else {
- Ext.Ajax.request({
- url: 'api/idnode/load',
- params: {
- uuid: r.id
- },
- success: function(d)
- {
- d = json_decode(d);
- var w = null;
- var c = {win: w};
- var p = tvheadend.idnode_editor(d[0], c);
- w = new Ext.Window({
- title: 'Edit ' + conf.titleS,
- layout: 'fit',
- autoWidth: true,
- autoHeight: true,
- plain: true,
- items: p
- });
- c.win = w;
- w.show();
- }
- });
+ if (!conf.readonly) {
+ if (buttons.length > 0)
+ buttons.push('-');
+ editBtn = new Ext.Toolbar.Button({
+ tooltip: 'Edit selected entry',
+ iconCls: 'edit',
+ text: 'Edit',
+ disabled: true,
+ handler: function() {
+ var r = select.getSelected();
+ if (r) {
+ if (conf.edittree) {
+ var p = tvheadend.idnode_tree({
+ url: 'api/idnode/tree',
+ params: {
+ root: r.id
+ }
+ });
+ p.setSize(800, 600);
+ var w = new Ext.Window({
+ title: 'Edit ' + conf.titleS,
+ layout: 'fit',
+ autoWidth: true,
+ autoHeight: true,
+ plain: true,
+ items: p
+ });
+ w.show();
+ } else {
+ var params = {
+ uuid: r.id,
+ meta: 1
+ };
+ if (conf.listEdit)
+ params['list'] = conf.listEdit;
+ tvheadend.Ajax({
+ url: 'api/idnode/load',
+ params: params,
+ success: function(d) {
+ d = json_decode(d);
+ var w = null;
+ var c = {win: w};
+ var p = tvheadend.idnode_editor(d[0], c);
+ w = new Ext.Window({
+ title: 'Edit ' + conf.titleS,
+ layout: 'fit',
+ autoWidth: true,
+ autoHeight: true,
+ plain: true,
+ items: p
+ });
+ c.win = w;
+ w.show();
+ }
+ });
+ }
}
}
- }
- });
- buttons.push(editBtn);
+ });
+ buttons.push(editBtn);
+ }
/* Hide Mode */
if (conf.hidemode) {
'->', '-', 'Per page', count]
});
plugins.push(filter);
- var grid = new Ext.grid.EditorGridPanel({
+ var gconf = {
stateful: true,
- stateId: conf.url,
+ stateId: conf.gridURL || conf.url,
stripeRows: true,
title: conf.titleP,
+ iconCls: conf.iconCls || '',
store: store,
cm: model,
selModel: select,
},
tbar: buttons,
bbar: page
- });
+ };
+ var grid = conf.readonly ? new Ext.grid.GridPanel(gconf) :
+ new Ext.grid.EditorGridPanel(gconf);
grid.on('filterupdate', function() {
page.changePage(0);
});
/* Request data */
if (!conf.fields) {
- Ext.Ajax.request({
+ var p = {};
+ if (conf.list) p['list'] = conf.list;
+ tvheadend.Ajax({
url: conf.url + '/class',
+ params: p,
success: function(d)
{
var d = json_decode(d);
}
};
+/*
+ * IDnode form grid
+ */
+tvheadend.idnode_form_grid = function(panel, conf)
+{
+ var buttons = [];
+ var plugins = conf.plugins || [];
+ var saveBtn = null;
+ var undoBtn = null;
+ var addBtn = null;
+ var delBtn = null;
+ var current = null;
+ var grid = null;
+ var mpanel = null;
+
+ /* Store */
+ var store = new Ext.data.JsonStore({
+ root: 'entries',
+ url: 'api/idnode/load',
+ baseParams: {
+ enum: 1,
+ 'class': conf.clazz
+ },
+ autoLoad: true,
+ id: 'key',
+ totalProperty: 'total',
+ fields: ['key','val'],
+ remoteSort: false,
+ pruneModifiedRecords: true,
+ sortInfo: {
+ field: 'val',
+ direction: 'ASC'
+ },
+ });
+
+ /* Model */
+ var model = new Ext.grid.ColumnModel({
+ defaultSortable: true,
+ columns: [{
+ width: 300,
+ id: 'val',
+ header: conf.titleC,
+ sortable: true,
+ dataIndex: 'val'
+ }]
+ });
+
+ /* Selection */
+ var select = new Ext.grid.RowSelectionModel({
+ singleSelect: true
+ });
+
+ /* Event handlers */
+ select.on('selectionchange', function(s) {
+ roweditor(s.getSelected());
+ if (conf.selected)
+ conf.selected(s);
+ });
+
+ /* Top bar */
+ saveBtn = new Ext.Toolbar.Button({
+ tooltip: 'Save pending changes (marked with red border)',
+ iconCls: 'save',
+ text: 'Save',
+ disabled: true,
+ handler: function() {
+ var node = current.editor.getForm().getFieldValues();
+ node.uuid = current.uuid;
+ tvheadend.Ajax({
+ url: 'api/idnode/save',
+ params: {
+ node: Ext.encode(node)
+ },
+ success: function() {
+ store.reload()
+ }
+ });
+ }
+ });
+ buttons.push(saveBtn);
+ undoBtn = new Ext.Toolbar.Button({
+ tooltip: 'Revert pending changes (marked with red border)',
+ iconCls: 'undo',
+ text: 'Undo',
+ disabled: true,
+ handler: function() {
+ if (current)
+ current.editor.getForm().reset();
+ }
+ });
+ buttons.push(undoBtn);
+ buttons.push('-');
+ if (conf.add) {
+ addBtn = new Ext.Toolbar.Button({
+ tooltip: 'Add a new entry',
+ iconCls: 'add',
+ text: 'Add',
+ disabled: false,
+ handler: function() {
+ tvheadend.idnode_create(conf.add, true);
+ }
+ });
+ buttons.push(addBtn);
+ }
+ if (conf.del) {
+ delBtn = new Ext.Toolbar.Button({
+ tooltip: 'Delete selected entries',
+ iconCls: 'remove',
+ text: 'Delete',
+ disabled: true,
+ handler: function() {
+ if (current) {
+ tvheadend.Ajax({
+ url: 'api/idnode/delete',
+ params: {
+ uuid: current.uuid
+ },
+ success: function(d)
+ {
+ store.reload();
+ grid.getSelectionModel().selectFirstRow();
+ }
+ });
+ }
+ }
+ });
+ buttons.push(delBtn);
+ }
+ if (conf.add || conf.del)
+ buttons.push('-');
+
+ /* Extra buttons */
+ if (conf.tbar) {
+ buttons.push('-');
+ for (i = 0; i < conf.tbar.length; i++) {
+ if (conf.tbar[i].callback) {
+ conf.tbar[i].handler = function(b, e) {
+ this.callback(this, e, store, select);
+ };
+ }
+ buttons.push(conf.tbar[i]);
+ }
+ }
+
+ /* Help */
+ if (conf.help) {
+ buttons.push('->');
+ buttons.push({
+ text: 'Help',
+ handler: conf.help
+ });
+ }
+
+ function roweditor(r) {
+ if (!r || !r.id)
+ return;
+ tvheadend.Ajax({
+ url: 'api/idnode/load',
+ params: {
+ uuid: r.id,
+ meta: 1
+ },
+ success: function(d) {
+ d = json_decode(d);
+ if (current)
+ mpanel.remove(current.editor);
+ var editor = new tvheadend.idnode_editor(d[0], {
+ title: 'Parameters',
+ labelWidth: 300,
+ fixedHeight: true,
+ help: conf.help || null,
+ inTabPanel: true,
+ noButtons: true
+ });
+ current = {
+ uuid: d[0].id,
+ editor: editor
+ }
+ saveBtn.setDisabled(false);
+ undoBtn.setDisabled(false);
+ delBtn.setDisabled(false);
+ mpanel.add(editor);
+ mpanel.doLayout();
+ }
+ });
+ }
+
+ /* Grid Panel (Selector) */
+ grid = new Ext.grid.GridPanel({
+ width: 200,
+ stripeRows: true,
+ store: store,
+ cm: model,
+ selModel: select,
+ plugins: plugins,
+ border: false,
+ viewConfig: {
+ forceFit: true
+ },
+ listeners : {
+ render : {
+ fn : function() {
+ if (!current)
+ grid.getSelectionModel().selectFirstRow();
+ }
+ }
+ }
+ });
+
+ mpanel = new Ext.Panel({
+ tbar: buttons,
+ title: conf.titleP || '',
+ iconCls: conf.iconCls || '',
+ layout: 'hbox',
+ padding: 5,
+ border: false,
+ layoutConfig: {
+ align: 'stretch'
+ },
+ items: [grid]
+ });
+
+ if (conf.tabIndex != null)
+ panel.insert(conf.tabIndex, mpanel);
+ else
+ panel.add(mpanel);
+
+ /* Add comet listeners */
+ var update = function(o) {
+ store.reload();
+ };
+ if (conf.comet)
+ tvheadend.comet.on(conf.comet, update);
+ tvheadend.comet.on('idnodeUpdated', update);
+ tvheadend.comet.on('idnodeDeleted', update);
+};
+
+/*
+ * IDNode Tree
+ */
tvheadend.idnode_tree = function(conf)
{
var current = null;
});
};
+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.loading = function(on) {
+ if (on)
+ Ext.getBody().mask('Loading... Please, wait...', 'loading');
+ else
+ Ext.getBody().unmask();
+};
+
/*
* General capabilities
*/
return;
if (o.dvr == true && tvheadend.dvrpanel == null) {
- tvheadend.dvrpanel = new tvheadend.dvr;
+ tvheadend.dvrpanel = tvheadend.dvr();
tvheadend.rootTabPanel.add(tvheadend.dvrpanel);
}
tabs1.push(tvheadend.conf_chepg);
/* DVR / Timeshift */
- tabs2 = [new tvheadend.dvrsettings];
- if (tvheadend.capabilities.indexOf('timeshift') !== -1) {
- tabs2.push(new tvheadend.timeshift);
- }
tvheadend.conf_tsdvr = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Recording',
iconCls: 'drive',
- items: tabs2
+ items: []
});
+ tvheadend.dvr_settings(tvheadend.conf_tsdvr, 0);
+ if (tvheadend.capabilities.indexOf('timeshift') !== -1)
+ tvheadend.conf_tsdvr.add(new tvheadend.timeshift);
tabs1.push(tvheadend.conf_tsdvr);
/* CSA */
htsbuf_queue_t *hq;
char buf[255];
dvr_entry_t *de;
- const char *host;
+ const char *host, *uuid;
off_t fsize;
time_t durration;
struct tm tm;
http_access_verify_channel(hc, ACCESS_RECORDER, de->de_channel))
continue;
+
durration = de->de_stop - de->de_start;
durration += (de->de_stop_extra + de->de_start_extra)*60;
bandwidth = ((8*fsize) / (durration*1024.0));
htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL));
htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration);
- htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%d,BANDWIDTH=%d\n", de->de_id, bandwidth);
+ uuid = idnode_uuid_as_str(&de->de_id);
+ htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%s,BANDWIDTH=%d\n", uuid, bandwidth);
htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf);
- snprintf(buf, sizeof(buf), "/dvrfile/%d", de->de_id);
+ snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid);
htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf,
access_ticket_create(buf));
}
{
htsbuf_queue_t *hq = &hc->hc_reply;
char buf[255];
- const char *ticket_id = NULL;
+ const char *ticket_id = NULL, *uuid;
time_t durration = 0;
off_t fsize = 0;
int bandwidth = 0;
htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL));
htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration);
- htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%d,BANDWIDTH=%d\n", de->de_id, bandwidth);
+ uuid = idnode_uuid_as_str(&de->de_id);
+ htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%s,BANDWIDTH=%d\n", uuid, bandwidth);
htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf);
- snprintf(buf, sizeof(buf), "/dvrfile/%d", de->de_id);
+ snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid);
ticket_id = access_ticket_create(buf);
htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id);
void extjs_start_v4l(void);
#endif
-void extjs_service_update(htsmsg_t *in);
-
-void extjs_service_delete(htsmsg_t *in);
-
void webui_api_init ( void );
}
/* Call */
- r = api_exec(remain, args, &resp);
+ r = api_exec(hc->hc_access, remain, args, &resp);
htsmsg_destroy(args);
/* Convert error */
if (r) {
switch (r) {
+ case EPERM:
case EACCES:
r = HTTP_STATUS_UNAUTHORIZED;
break;