diff --git a/Makefile b/Makefile
index 632fdf00..b75b9936 100644
--- a/Makefile
+++ b/Makefile
@@ -178,6 +178,7 @@ SRCS += src/plumbing/tsfix.c \
SRCS += src/dvr/dvr_db.c \
src/dvr/dvr_rec.c \
src/dvr/dvr_autorec.c \
+ src/dvr/dvr_timerec.c \
src/dvr/dvr_cutpoints.c \
SRCS += src/webui/webui.c \
diff --git a/docs/html/config_dvrtime.html b/docs/html/config_dvrtime.html
new file mode 100644
index 00000000..6dc9af74
--- /dev/null
+++ b/docs/html/config_dvrtime.html
@@ -0,0 +1,10 @@
+
+
+
+ This tab is used to manipulate with the Digital Video Recorder entries -
+ the time-based automatic recording.
+
+
+ A volunteer required to fill this...
+
+
diff --git a/src/api/api_dvr.c b/src/api/api_dvr.c
index 34b6ee36..e1e781bf 100644
--- a/src/api/api_dvr.c
+++ b/src/api/api_dvr.c
@@ -330,6 +330,40 @@ api_dvr_autorec_create_by_series
return !count ? EINVAL : 0;
}
+static void
+api_dvr_timerec_grid
+ ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
+{
+ dvr_timerec_entry_t *dte;
+
+ TAILQ_FOREACH(dte, &timerec_entries, dte_link)
+ idnode_set_add(ins, (idnode_t*)dte, &conf->filter);
+}
+
+static int
+api_dvr_timerec_create
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ htsmsg_t *conf;
+ dvr_timerec_entry_t *dte;
+
+ if (!(conf = htsmsg_get_map(args, "conf")))
+ return EINVAL;
+
+ if (perm->aa_representative)
+ htsmsg_set_str(conf, "creator", perm->aa_representative);
+
+ pthread_mutex_lock(&global_lock);
+ dte = dvr_timerec_create(NULL, conf);
+ if (dte) {
+ dvr_timerec_save(dte);
+ dvr_timerec_check(dte);
+ }
+ pthread_mutex_unlock(&global_lock);
+
+ return 0;
+}
+
void api_dvr_init ( void )
{
static api_hook_t ah[] = {
@@ -351,6 +385,10 @@ void api_dvr_init ( void )
{ "dvr/autorec/create", ACCESS_RECORDER, api_dvr_autorec_create, NULL },
{ "dvr/autorec/create_by_series", ACCESS_RECORDER, api_dvr_autorec_create_by_series, NULL },
+ { "dvr/timerec/class", ACCESS_RECORDER, api_idnode_class, (void*)&dvr_timerec_entry_class },
+ { "dvr/timerec/grid", ACCESS_RECORDER, api_idnode_grid, api_dvr_timerec_grid },
+ { "dvr/timerec/create", ACCESS_RECORDER, api_dvr_timerec_create, NULL },
+
{ NULL },
};
diff --git a/src/channels.c b/src/channels.c
index 4c5eb837..886b26c0 100644
--- a/src/channels.c
+++ b/src/channels.c
@@ -197,6 +197,17 @@ channel_class_get_title ( idnode_t *self )
return channel_get_name((channel_t*)self);
}
+/* exported for others */
+htsmsg_t *
+channel_class_get_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;
+}
+
static const void *
channel_class_get_name ( void *p )
{
@@ -531,6 +542,12 @@ channel_create0
{
lock_assert(&global_lock);
+ LIST_INIT(&ch->ch_services);
+ LIST_INIT(&ch->ch_subscriptions);
+ LIST_INIT(&ch->ch_epggrab);
+ LIST_INIT(&ch->ch_autorecs);
+ LIST_INIT(&ch->ch_timerecs);
+
if (idnode_insert(&ch->ch_id, uuid, idc, IDNODE_SHORT_UUID)) {
if (uuid)
tvherror("channel", "invalid uuid '%s'", uuid);
@@ -578,6 +595,7 @@ channel_delete ( channel_t *ch, int delconf )
/* DVR */
autorec_destroy_by_channel(ch, delconf);
+ timerec_destroy_by_channel(ch, delconf);
dvr_destroy_by_channel(ch, delconf);
/* Services */
diff --git a/src/channels.h b/src/channels.h
index 32742d33..23755eb6 100644
--- a/src/channels.h
+++ b/src/channels.h
@@ -71,6 +71,7 @@ typedef struct channel
int ch_dvr_extra_time_post;
struct dvr_entry_list ch_dvrs;
struct dvr_autorec_entry_list ch_autorecs;
+ struct dvr_timerec_entry_list ch_timerecs;
} channel_t;
@@ -152,6 +153,8 @@ channel_t *channel_find_by_number(int no);
#define channel_find channel_find_by_uuid
+htsmsg_t * channel_class_get_list(void *o);
+
int channel_set_tags_by_list ( channel_t *ch, htsmsg_t *tags );
int channel_set_services_by_list ( channel_t *ch, htsmsg_t *svcs );
diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h
index f4dba1a7..b004e136 100644
--- a/src/dvr/dvr.h
+++ b/src/dvr/dvr.h
@@ -194,6 +194,11 @@ typedef struct dvr_entry {
LIST_ENTRY(dvr_entry) de_autorec_link;
struct dvr_autorec_entry *de_autorec;
+ /**
+ * Timerec linkage
+ */
+ struct dvr_timerec_entry *de_timerec;
+
/**
* Fields for recording
*/
@@ -251,7 +256,7 @@ typedef struct dvr_autorec_entry {
channel_tag_t *dae_channel_tag;
LIST_ENTRY(dvr_autorec_entry) dae_channel_tag_link;
- dvr_prio_t dae_pri;
+ int dae_pri;
struct dvr_entry_list dae_spawns;
@@ -272,6 +277,42 @@ TAILQ_HEAD(dvr_autorec_entry_queue, dvr_autorec_entry);
extern struct dvr_autorec_entry_queue autorec_entries;
+/**
+ * Timerec entry
+ */
+typedef struct dvr_timerec_entry {
+ idnode_t dte_id;
+
+ TAILQ_ENTRY(dvr_timerec_entry) dte_link;
+
+ char *dte_name;
+ char *dte_config_name;
+
+ int dte_enabled;
+ char *dte_creator;
+ char *dte_comment;
+
+ char *dte_title;
+
+ int dte_start; /* Minutes from midnight */
+ int dte_stop; /* Minutes from midnight */
+
+ uint32_t dte_weekdays;
+
+ channel_t *dte_channel;
+ LIST_ENTRY(dvr_timerec_entry) dte_channel_link;
+
+ int dte_pri;
+
+ dvr_entry_t *dte_spawn;
+
+ int dte_retention;
+} dvr_timerec_entry_t;
+
+TAILQ_HEAD(dvr_timerec_entry_queue, dvr_timerec_entry);
+
+extern struct dvr_timerec_entry_queue timerec_entries;
+
/**
*
*/
@@ -279,6 +320,7 @@ 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;
+extern const idclass_t dvr_timerec_entry_class;
/**
* Prototypes
@@ -366,12 +408,6 @@ void dvr_config_init(void);
void dvr_done(void);
-void dvr_autorec_init(void);
-
-void dvr_autorec_done(void);
-
-void dvr_autorec_update(void);
-
void dvr_destroy_by_channel(channel_t *ch, int delconf);
void dvr_rec_subscribe(dvr_entry_t *de);
@@ -436,6 +472,16 @@ int dvr_sort_start_ascending(const void *A, const void *B);
dvr_autorec_entry_t *
dvr_autorec_create(const char *uuid, htsmsg_t *conf);
+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_timerec_entry_t *tae,
+ dvr_prio_t pri, int retention);
+
dvr_autorec_entry_t*
dvr_autorec_create_htsp(const char *dvr_config_name, const char *title,
channel_t *ch, uint32_t aroundTime, uint32_t days,
@@ -458,18 +504,53 @@ dvr_autorec_find_by_uuid(const char *uuid)
{ return (dvr_autorec_entry_t*)idnode_find(uuid, &dvr_autorec_entry_class); }
+htsmsg_t * dvr_autorec_entry_class_time_list(void *o, const char *null);
+htsmsg_t * dvr_autorec_entry_class_weekdays_list ( void *o );
+char * dvr_autorec_entry_class_weekdays_rend(uint32_t weekdays);
+
void dvr_autorec_check_event(epg_broadcast_t *e);
void dvr_autorec_check_brand(epg_brand_t *b);
void dvr_autorec_check_season(epg_season_t *s);
void dvr_autorec_check_serieslink(epg_serieslink_t *s);
-
void autorec_destroy_by_channel(channel_t *ch, int delconf);
void autorec_destroy_by_channel_tag(channel_tag_t *ct, int delconf);
void autorec_destroy_by_id(const char *id, int delconf);
+void dvr_autorec_init(void);
+
+void dvr_autorec_done(void);
+
+void dvr_autorec_update(void);
+
+/**
+ *
+ */
+
+dvr_timerec_entry_t *
+dvr_timerec_create(const char *uuid, htsmsg_t *conf);
+
+static inline dvr_timerec_entry_t *
+dvr_timerec_find_by_uuid(const char *uuid)
+ { return (dvr_timerec_entry_t*)idnode_find(uuid, &dvr_timerec_entry_class); }
+
+
+void dvr_timerec_save(dvr_timerec_entry_t *dae);
+
+void dvr_timerec_check(dvr_timerec_entry_t *dae);
+
+void timerec_destroy_by_channel(channel_t *ch, int delconf);
+
+void timerec_destroy_by_id(const char *id, int delconf);
+
+void dvr_timerec_init(void);
+
+void dvr_timerec_done(void);
+
+void dvr_timerec_update(void);
+
/**
*
*/
diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c
index d2f7078a..7c7a9ed3 100644
--- a/src/dvr/dvr_autorec.c
+++ b/src/dvr/dvr_autorec.c
@@ -385,16 +385,6 @@ dvr_autorec_entry_class_channel_get(void *o)
return &ret;
}
-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;
-}
-
static int
dvr_autorec_entry_class_title_set(void *o, const void *v)
{
@@ -477,14 +467,6 @@ dvr_autorec_entry_class_start_set(void *o, const void *v)
return dvr_autorec_entry_class_time_set(o, v, &dae->dae_start);
}
-#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)
{
@@ -505,22 +487,13 @@ dvr_autorec_entry_class_start_get(void *o)
return dvr_autorec_entry_class_time_get(o, dae->dae_start);
}
-#if 0
-static int
-dvr_autorec_entry_class_stop_get(void *o)
-{
- dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
- return dvr_autorec_entry_class_time_get(o, v, &dae->dae_stop);
-}
-#endif
-
-static htsmsg_t *
-dvr_autorec_entry_class_time_list(void *o)
+htsmsg_t *
+dvr_autorec_entry_class_time_list(void *o, const char *null)
{
int i;
htsmsg_t *l = htsmsg_create_list();
char buf[16];
- htsmsg_add_str(l, NULL, "Any");
+ htsmsg_add_str(l, NULL, null);
for (i = 0; i < 24*60; i += 10) {
snprintf(buf, sizeof(buf), "%02d:%02d", i / 60, (i % 60));
htsmsg_add_str(l, NULL, buf);
@@ -528,6 +501,12 @@ dvr_autorec_entry_class_time_list(void *o)
return l;
}
+static htsmsg_t *
+dvr_autorec_entry_class_time_list_(void *o)
+{
+ return dvr_autorec_entry_class_time_list(o, "Any");
+}
+
static htsmsg_t *
dvr_autorec_entry_class_minduration_list(void *o)
{
@@ -597,27 +576,26 @@ static const struct strtab dvr_autorec_entry_class_weekdays_tab[] = {
{ "Sun", 7 },
};
-static htsmsg_t *
+htsmsg_t *
dvr_autorec_entry_class_weekdays_list ( void *o )
{
return strtab2htsmsg(dvr_autorec_entry_class_weekdays_tab);
}
-static char *
-dvr_autorec_entry_class_weekdays_rend(void *o)
+char *
+dvr_autorec_entry_class_weekdays_rend(uint32_t weekdays)
{
- dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
char buf[32];
size_t l;
int i;
- if (dae->dae_weekdays == 0x7f)
+ if (weekdays == 0x7f)
strcpy(buf + 1, "All days");
- else if (dae->dae_weekdays == 0)
+ else if (weekdays == 0)
strcpy(buf + 1, "No days");
else {
buf[0] = '\0';
for (i = 0; i < 7; i++)
- if (dae->dae_weekdays & (1 << i)) {
+ if (weekdays & (1 << i)) {
l = strlen(buf);
snprintf(buf + l, sizeof(buf) - l, ",%s",
val2str(i + 1, dvr_autorec_entry_class_weekdays_tab));
@@ -626,6 +604,13 @@ dvr_autorec_entry_class_weekdays_rend(void *o)
return strdup(buf + 1);
}
+static char *
+dvr_autorec_entry_class_weekdays_rend_(void *o)
+{
+ dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
+ return dvr_autorec_entry_class_weekdays_rend(dae->dae_weekdays);
+}
+
static int
dvr_autorec_entry_class_brand_set(void *o, const void *v)
{
@@ -776,7 +761,7 @@ const idclass_t dvr_autorec_entry_class = {
.name = "Channel",
.set = dvr_autorec_entry_class_channel_set,
.get = dvr_autorec_entry_class_channel_get,
- .list = dvr_autorec_entry_class_channel_list,
+ .list = channel_class_get_list,
},
{
.type = PT_STR,
@@ -792,7 +777,7 @@ const idclass_t dvr_autorec_entry_class = {
.name = "Starting Around",
.set = dvr_autorec_entry_class_start_set,
.get = dvr_autorec_entry_class_start_get,
- .list = dvr_autorec_entry_class_time_list,
+ .list = dvr_autorec_entry_class_time_list_,
},
{
.type = PT_TIME,
@@ -816,7 +801,8 @@ const idclass_t dvr_autorec_entry_class = {
.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,
+ .rend = dvr_autorec_entry_class_weekdays_rend_,
+ .def.u32 = 0x7f
},
{
.type = PT_INT,
diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c
index 17ec5b31..972992cf 100644
--- a/src/dvr/dvr_db.c
+++ b/src/dvr/dvr_db.c
@@ -74,6 +74,8 @@ dvr_entry_get_extra_time_pre( dvr_entry_t *de )
{
time_t extra = de->de_start_extra;
+ if (de->de_timerec)
+ return 0;
if (!extra_valid(extra)) {
if (de->de_channel)
extra = de->de_channel->ch_dvr_extra_time_pre;
@@ -88,6 +90,8 @@ dvr_entry_get_extra_time_post( dvr_entry_t *de )
{
time_t extra = de->de_stop_extra;
+ if (de->de_timerec)
+ return 0;
if (!extra_valid(extra)) {
if (de->de_channel)
extra = de->de_channel->ch_dvr_extra_time_post;
@@ -443,13 +447,14 @@ dvr_entry_create(const char *uuid, htsmsg_t *conf)
/**
* Create the event
*/
-static dvr_entry_t *
-_dvr_entry_create(const char *config_uuid, epg_broadcast_t *e,
+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_timerec_entry_t *dte,
dvr_prio_t pri, int retention)
{
dvr_entry_t *de;
@@ -497,6 +502,8 @@ _dvr_entry_create(const char *config_uuid, epg_broadcast_t *e,
htsmsg_add_u32(conf, "broadcast", e->id);
if (dae)
htsmsg_add_str(conf, "autorec", idnode_uuid_as_str(&dae->dae_id));
+ if (dte)
+ htsmsg_add_str(conf, "timerec", idnode_uuid_as_str(&dte->dte_id));
de = dvr_entry_create(NULL, conf);
@@ -535,11 +542,11 @@ dvr_entry_create_htsp(const char *config_uuid,
dvr_config_t *cfg = dvr_config_find_by_uuid(config_uuid);
if (!cfg)
cfg = dvr_config_find_by_name(config_uuid);
- return _dvr_entry_create(cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL,
+ return dvr_entry_create_(cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL,
NULL,
ch, start, stop, start_extra, stop_extra,
title, description, lang, content_type,
- creator, dae, pri, retention);
+ creator, dae, NULL, pri, retention);
}
/**
@@ -555,12 +562,12 @@ dvr_entry_create_by_event(const char *config_uuid,
if(!e->channel || !e->episode || !e->episode->title)
return NULL;
- return _dvr_entry_create(config_uuid, e,
+ return dvr_entry_create_(config_uuid, e,
e->channel, e->start, e->stop,
start_extra, stop_extra,
NULL, NULL, NULL,
LIST_FIRST(&e->episode->genre),
- creator, dae, pri, retention);
+ creator, dae, NULL, pri, retention);
}
/**
@@ -637,6 +644,11 @@ dvr_entry_dec_ref(dvr_entry_t *de)
if(de->de_autorec != NULL)
LIST_REMOVE(de, de_autorec_link);
+ if (de->de_timerec) {
+ de->de_timerec->dte_spawn = NULL;
+ de->de_timerec = NULL;
+ }
+
if(de->de_config != NULL)
LIST_REMOVE(de, de_config_link);
@@ -1272,16 +1284,6 @@ dvr_entry_class_channel_get(void *o)
return &ret;
}
-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;
-}
-
static int
dvr_entry_class_channel_name_set(void *o, const void *v)
{
@@ -1417,6 +1419,40 @@ dvr_entry_class_autorec_get(void *o)
return &ret;
}
+static int
+dvr_entry_class_timerec_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ dvr_timerec_entry_t *dte;
+ if (!dvr_entry_is_editable(de))
+ return 0;
+ dte = v ? dvr_timerec_find_by_uuid(v) : NULL;
+ if (dte == NULL) {
+ if (de->de_timerec) {
+ de->de_timerec->dte_spawn = NULL;
+ de->de_timerec = NULL;
+ return 1;
+ }
+ } else if (de->de_timerec != dte) {
+ de->de_timerec = dte;
+ dte->dte_spawn = de;
+ return 1;
+ }
+ return 0;
+}
+
+static const void *
+dvr_entry_class_timerec_get(void *o)
+{
+ static const char *ret;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_timerec)
+ ret = idnode_uuid_as_str(&de->de_timerec->dte_id);
+ else
+ ret = "";
+ return &ret;
+}
+
static int
dvr_entry_class_broadcast_set(void *o, const void *v)
{
@@ -1732,7 +1768,7 @@ const idclass_t dvr_entry_class = {
.name = "Channel",
.set = dvr_entry_class_channel_set,
.get = dvr_entry_class_channel_get,
- .list = dvr_entry_class_channel_list,
+ .list = channel_class_get_list,
.get_opts = dvr_entry_class_start_opts,
},
{
@@ -1863,6 +1899,14 @@ const idclass_t dvr_entry_class = {
.get = dvr_entry_class_autorec_get,
.opts = PO_RDONLY,
},
+ {
+ .type = PT_STR,
+ .id = "timerec",
+ .name = "Auto Time Record",
+ .set = dvr_entry_class_timerec_set,
+ .get = dvr_entry_class_timerec_get,
+ .opts = PO_RDONLY,
+ },
{
.type = PT_U32,
.id = "content_type",
@@ -2755,8 +2799,10 @@ dvr_init(void)
dvr_inotify_init();
#endif
dvr_autorec_init();
+ dvr_timerec_init();
dvr_db_load();
dvr_autorec_update();
+ dvr_timerec_update();
}
/**
@@ -2778,4 +2824,5 @@ dvr_done(void)
dvr_config_destroy(cfg, 0);
pthread_mutex_unlock(&global_lock);
dvr_autorec_done();
+ dvr_timerec_done();
}
diff --git a/src/dvr/dvr_timerec.c b/src/dvr/dvr_timerec.c
new file mode 100644
index 00000000..7dbfc1d4
--- /dev/null
+++ b/src/dvr/dvr_timerec.c
@@ -0,0 +1,606 @@
+/*
+ * tvheadend, Automatic time-based recording
+ * Copyright (C) 2014 Jaroslav Kysela