Add support for autorecording rules
This commit is contained in:
parent
f043211140
commit
dc86a566fa
5 changed files with 449 additions and 21 deletions
283
autorec.c
283
autorec.c
|
@ -25,9 +25,8 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
@ -37,13 +36,289 @@
|
|||
#include "tvhead.h"
|
||||
#include "dispatch.h"
|
||||
#include "autorec.h"
|
||||
#include "pvr.h"
|
||||
#include "epg.h"
|
||||
#include "channels.h"
|
||||
|
||||
struct autorec_head autorecs;
|
||||
static int ar_id_ceil;
|
||||
|
||||
static void autorec_save_entry(autorec_t *ar);
|
||||
static void autorec_load(void);
|
||||
|
||||
void
|
||||
autorec_init(void)
|
||||
{
|
||||
TAILQ_INIT(&autorecs);
|
||||
autorec_load();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return 1 if the event 'e' is matched by the autorec rule 'ar'
|
||||
*/
|
||||
int
|
||||
autorec_cmp(autorec_t *ar, event_t *e)
|
||||
{
|
||||
if(ar->ar_ch != NULL && ar->ar_ch != e->e_ch)
|
||||
return 0;
|
||||
|
||||
if(ar->ar_tcg != NULL && ar->ar_tcg != e->e_ch->ch_group)
|
||||
return 0;
|
||||
|
||||
if(ar->ar_ecg != NULL) {
|
||||
if(e->e_content_type == NULL || ar->ar_ecg != e->e_content_type->ect_group)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(ar->ar_title != NULL &&
|
||||
regexec(&ar->ar_title_preg, e->e_title, 0, NULL, 0))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
autorec_tag(autorec_t *ar, event_t *e)
|
||||
{
|
||||
char creator[200];
|
||||
|
||||
snprintf(creator, sizeof(creator), "autorec: %s", ar->ar_name);
|
||||
pvr_schedule_by_event(e, creator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the current event and the next after that on all channels
|
||||
*/
|
||||
static void
|
||||
autorec_check_new_ar(autorec_t *ar)
|
||||
{
|
||||
event_t *e;
|
||||
th_channel_t *ch;
|
||||
|
||||
epg_lock();
|
||||
|
||||
LIST_FOREACH(ch, &channels, ch_global_link) {
|
||||
e = ch->ch_epg_cur_event;
|
||||
if(e == NULL)
|
||||
continue;
|
||||
|
||||
if(autorec_cmp(ar, e))
|
||||
autorec_tag(ar, e);
|
||||
|
||||
e = TAILQ_NEXT(e, e_link);
|
||||
if(e == NULL)
|
||||
continue;
|
||||
|
||||
if(autorec_cmp(ar, e))
|
||||
autorec_tag(ar, e);
|
||||
}
|
||||
epg_unlock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For the given event, check if any of the autorec matches
|
||||
*
|
||||
* Assumes epg_lock() is held
|
||||
*/
|
||||
void
|
||||
autorec_check_new_event(event_t *e)
|
||||
{
|
||||
autorec_t *ar;
|
||||
|
||||
TAILQ_FOREACH(ar, &autorecs, ar_link) {
|
||||
if(autorec_cmp(ar, e))
|
||||
autorec_tag(ar, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a new autorec rule, return -1 on failure, internal func
|
||||
*/
|
||||
static autorec_t *
|
||||
autorec_create0(const char *name, int prio, const char *title,
|
||||
epg_content_group_t *ecg, th_channel_group_t *tcg,
|
||||
th_channel_t *ch, int id, const char *creator)
|
||||
{
|
||||
autorec_t *ar = calloc(1, sizeof(autorec_t));
|
||||
|
||||
if(title != NULL) {
|
||||
ar->ar_title = strdup(title);
|
||||
if(regcomp(&ar->ar_title_preg, title,
|
||||
REG_ICASE | REG_EXTENDED | REG_NOSUB))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ar->ar_name = strdup(name);
|
||||
ar->ar_rec_prio = prio;
|
||||
ar->ar_ecg = ecg;
|
||||
ar->ar_tcg = tcg;
|
||||
ar->ar_ch = ch;
|
||||
ar->ar_creator = strdup(creator);
|
||||
|
||||
ar->ar_id = id;
|
||||
TAILQ_INSERT_TAIL(&autorecs, ar, ar_link);
|
||||
autorec_check_new_ar(ar);
|
||||
return ar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and remove the given autorec
|
||||
*/
|
||||
static void
|
||||
autorec_destroy(autorec_t *ar)
|
||||
{
|
||||
char buf[400];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/autorec/%d", settings_dir, ar->ar_id);
|
||||
unlink(buf);
|
||||
|
||||
if(ar->ar_title != NULL) {
|
||||
regfree(&ar->ar_title_preg);
|
||||
free((void *)ar->ar_title);
|
||||
}
|
||||
free((void *)ar->ar_creator);
|
||||
free((void *)ar->ar_name);
|
||||
|
||||
TAILQ_REMOVE(&autorecs, ar, ar_link);
|
||||
free(ar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new autorec rule, return -1 on failure
|
||||
*/
|
||||
int
|
||||
autorec_create(const char *name, int prio, const char *title,
|
||||
epg_content_group_t *ecg, th_channel_group_t *tcg,
|
||||
th_channel_t *ch)
|
||||
th_channel_t *ch, const char *creator)
|
||||
{
|
||||
|
||||
autorec_t *ar;
|
||||
|
||||
ar_id_ceil++;
|
||||
ar = autorec_create0(name, prio, title, ecg, tcg, ch, ar_id_ceil, creator);
|
||||
if(ar == NULL)
|
||||
return -1;
|
||||
|
||||
autorec_save_entry(ar);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the given id, delete the autorec entry
|
||||
*/
|
||||
void
|
||||
autorec_delete_by_id(int id)
|
||||
{
|
||||
autorec_t *ar;
|
||||
|
||||
TAILQ_FOREACH(ar, &autorecs, ar_link)
|
||||
if(ar->ar_id == id)
|
||||
break;
|
||||
|
||||
if(ar == NULL)
|
||||
return;
|
||||
|
||||
autorec_destroy(ar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store an autorec entry on disk
|
||||
*/
|
||||
static void
|
||||
autorec_save_entry(autorec_t *ar)
|
||||
{
|
||||
char buf[400];
|
||||
FILE *fp;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/autorec/%d", settings_dir, ar->ar_id);
|
||||
|
||||
if((fp = settings_open_for_write(buf)) == NULL)
|
||||
return;
|
||||
|
||||
fprintf(fp, "name = %s\n", ar->ar_name);
|
||||
fprintf(fp, "rec_prio = %d\n", ar->ar_rec_prio);
|
||||
fprintf(fp, "creator = %s\n", ar->ar_creator);
|
||||
if(ar->ar_title != NULL)
|
||||
fprintf(fp, "event_title = %s\n", ar->ar_title);
|
||||
|
||||
if(ar->ar_ecg != NULL)
|
||||
fprintf(fp, "event_content_group = %s\n", ar->ar_ecg->ecg_name);
|
||||
|
||||
if(ar->ar_tcg != NULL)
|
||||
fprintf(fp, "channel_group = %s\n", ar->ar_tcg->tcg_name);
|
||||
|
||||
if(ar->ar_ch != NULL)
|
||||
fprintf(fp, "channel = %s\n", ar->ar_ch->ch_name);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all entries from disk
|
||||
*/
|
||||
static void
|
||||
autorec_load(void)
|
||||
{
|
||||
struct config_head cl;
|
||||
char buf[400];
|
||||
struct dirent *d;
|
||||
const char *name, *title, *contentgroup, *chgroup, *channel, *creator;
|
||||
DIR *dir;
|
||||
int prio, id;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/autorec", settings_dir);
|
||||
|
||||
if((dir = opendir(buf)) == NULL)
|
||||
return;
|
||||
|
||||
while((d = readdir(dir)) != NULL) {
|
||||
|
||||
if(d->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/autorec/%s", settings_dir, d->d_name);
|
||||
|
||||
TAILQ_INIT(&cl);
|
||||
config_read_file0(buf, &cl);
|
||||
|
||||
/* Required */
|
||||
name = config_get_str_sub(&cl, "name", NULL);
|
||||
creator = config_get_str_sub(&cl, "creator", NULL);
|
||||
prio = atoi(config_get_str_sub(&cl, "rec_prio", "250"));
|
||||
/* Optional */
|
||||
title = config_get_str_sub(&cl, "event_title", NULL);
|
||||
contentgroup = config_get_str_sub(&cl, "event_content_group", NULL);
|
||||
chgroup = config_get_str_sub(&cl, "channel_group", NULL);
|
||||
channel = config_get_str_sub(&cl, "channel", NULL);
|
||||
|
||||
if(name != NULL && prio > 0 && creator != NULL) {
|
||||
id = atoi(d->d_name);
|
||||
|
||||
autorec_create0(name, prio, title,
|
||||
|
||||
contentgroup ?
|
||||
epg_content_group_find_by_name(contentgroup) : NULL,
|
||||
|
||||
chgroup ? channel_group_find(chgroup, 1) : NULL,
|
||||
|
||||
channel ? channel_find(channel, 1, NULL) : NULL,
|
||||
id, creator);
|
||||
|
||||
if(id > ar_id_ceil)
|
||||
ar_id_ceil = id;
|
||||
|
||||
}
|
||||
config_free0(&cl);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
|
37
autorec.h
37
autorec.h
|
@ -19,8 +19,39 @@
|
|||
#ifndef AUTOREC_H
|
||||
#define AUTOREC_H
|
||||
|
||||
void autorec_create(const char *name, int prio, const char *title,
|
||||
epg_content_group_t *ecg, th_channel_group_t *tcg,
|
||||
th_channel_t *ch);
|
||||
#include <regex.h>
|
||||
|
||||
TAILQ_HEAD(autorec_head, autorec);
|
||||
|
||||
extern struct autorec_head autorecs;
|
||||
|
||||
typedef struct autorec {
|
||||
TAILQ_ENTRY(autorec) ar_link;
|
||||
|
||||
int ar_id;
|
||||
|
||||
const char *ar_creator;
|
||||
const char *ar_name;
|
||||
int ar_rec_prio;
|
||||
|
||||
const char *ar_title;
|
||||
regex_t ar_title_preg;
|
||||
|
||||
epg_content_group_t *ar_ecg;
|
||||
th_channel_group_t *ar_tcg;
|
||||
th_channel_t *ar_ch;
|
||||
|
||||
} autorec_t;
|
||||
|
||||
|
||||
void autorec_init(void);
|
||||
|
||||
int autorec_create(const char *name, int prio, const char *title,
|
||||
epg_content_group_t *ecg, th_channel_group_t *tcg,
|
||||
th_channel_t *ch, const char *creator);
|
||||
|
||||
void autorec_check_new_event(event_t *e);
|
||||
|
||||
void autorec_delete_by_id(int id);
|
||||
|
||||
#endif /* AUTOREC_H */
|
||||
|
|
12
epg.c
12
epg.c
|
@ -28,6 +28,7 @@
|
|||
#include "dispatch.h"
|
||||
#include "htsclient.h"
|
||||
#include "htsp.h"
|
||||
#include "autorec.h"
|
||||
|
||||
#define EPG_MAX_AGE 86400
|
||||
|
||||
|
@ -399,6 +400,17 @@ epg_set_current_event(th_channel_t *ch, event_t *e)
|
|||
clients_send_ref(ch->ch_tag);
|
||||
|
||||
htsp_async_channel_update(ch);
|
||||
|
||||
if(e == NULL)
|
||||
return;
|
||||
/* Let autorecorder check this event */
|
||||
autorec_check_new_event(e);
|
||||
|
||||
e = TAILQ_NEXT(e, e_link);
|
||||
/* .. and next one, to make better scheduling */
|
||||
|
||||
if(e != NULL)
|
||||
autorec_check_new_event(e);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
130
htmlui.c
130
htmlui.c
|
@ -834,6 +834,7 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
int divid = 1;
|
||||
int size = 600;
|
||||
http_arg_t *ra;
|
||||
autorec_t *ar, *ar_show = NULL;
|
||||
|
||||
if(!html_verify_access(hc, "record-events"))
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
|
@ -845,7 +846,15 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
pvrr_tgt = NULL;
|
||||
LIST_FOREACH(ra, &hc->hc_url_args, link) {
|
||||
c = 0;
|
||||
if(!strncmp(ra->key, "clear_", 6)) {
|
||||
|
||||
if(!strncmp(ra->key, "ardel_", 6)) {
|
||||
txt = ra->key + 6;
|
||||
autorec_delete_by_id(atoi(txt));
|
||||
/* Redirect back to ourself, to get rid of URL cruft */
|
||||
http_redirect(hc, "/pvr");
|
||||
return 0;
|
||||
|
||||
} else if(!strncmp(ra->key, "clear_", 6)) {
|
||||
txt = ra->key + 6;
|
||||
} else if(!strncmp(ra->key, "desched_", 8)) {
|
||||
txt = ra->key + 8;
|
||||
|
@ -883,12 +892,14 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
|
||||
tcp_qprintf(&tq, "<form method=\"get\" action=\"/pvr\">");
|
||||
|
||||
box_top(&tq, "box");
|
||||
/*
|
||||
* PVR log
|
||||
*/
|
||||
|
||||
box_top(&tq, "box");
|
||||
tcp_qprintf(&tq,
|
||||
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
|
||||
"Recorder Log");
|
||||
|
||||
tcp_qprintf(&tq, "<div class=\"content\">");
|
||||
|
||||
c = 0;
|
||||
|
@ -981,6 +992,14 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
"<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" "
|
||||
"style=\"font: 85% Verdana, Arial, Helvetica, sans-serif\">");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Created by:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
pvrr->pvrr_creator ?: "<i>not set</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
|
@ -997,14 +1016,6 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
"</tr>",
|
||||
val2str(pvrr->pvrr_rec_status, recintstatustxt) ?: "invalid");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Created by:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
pvrr->pvrr_creator ?: "<i>not set</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"</table>");
|
||||
|
||||
|
@ -1042,6 +1053,100 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
tcp_qprintf(&tq, "</div>\r\n");
|
||||
|
||||
box_bottom(&tq);
|
||||
tcp_qprintf(&tq, "<br>\r\n");
|
||||
|
||||
/*
|
||||
* Autorecorder
|
||||
*/
|
||||
|
||||
box_top(&tq, "box");
|
||||
tcp_qprintf(&tq,
|
||||
"<div class=\"contentbig\"><center><b>%s</b></center></div>",
|
||||
"Autorecorder ruleset");
|
||||
tcp_qprintf(&tq, "<div class=\"content\">\r\n");
|
||||
|
||||
if(TAILQ_FIRST(&autorecs) == NULL)
|
||||
tcp_qprintf(&tq, "<center>No entries</center><br>");
|
||||
|
||||
TAILQ_FOREACH(ar, &autorecs, ar_link) {
|
||||
tcp_qprintf(&tq,
|
||||
"<div><a href=\"javascript:expcol('div%d')\">"
|
||||
"%s</a></div><br>\r\n",
|
||||
divid, ar->ar_name);
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<div id=\"div%d\" style=\"display:%s; "
|
||||
"border-bottom-width:thin; border-bottom-style:solid\">",
|
||||
divid, ar_show == ar ? "block" : "none");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" "
|
||||
"style=\"font: 85% Verdana, Arial, Helvetica, sans-serif\">");
|
||||
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Created by:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
ar->ar_creator ?: "<i>not set</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Recording priority:</span><td>"
|
||||
"<td>%d</td>"
|
||||
"</tr>",
|
||||
ar->ar_rec_prio);
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Event tile:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
ar->ar_title ?: "<i>All</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Content group:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
ar->ar_ecg ? ar->ar_ecg->ecg_name: "<i>All</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Channel group:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
ar->ar_tcg ? ar->ar_tcg->tcg_name: "<i>All</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"<tr>"
|
||||
"<td width=125><span style=\"text-align: right\">"
|
||||
"Channel:</span><td>"
|
||||
"<td>%s</td>"
|
||||
"</tr>",
|
||||
ar->ar_ch ? ar->ar_ch->ch_name: "<i>All</i>");
|
||||
|
||||
tcp_qprintf(&tq,
|
||||
"</table>");
|
||||
|
||||
tcp_qprintf(&tq,"<div style=\"text-align: center\">"
|
||||
"<input type=\"submit\" class=\"knapp\" name=\"ardel_%d\" "
|
||||
"value=\"Delete rule\"></div>", ar->ar_id);
|
||||
|
||||
tcp_qprintf(&tq, "<br></div>\n");
|
||||
|
||||
|
||||
divid++;
|
||||
}
|
||||
tcp_qprintf(&tq, "<br></div>\r\n");
|
||||
box_bottom(&tq);
|
||||
|
||||
tcp_qprintf(&tq, "</form>\r\n");
|
||||
html_footer(&tq);
|
||||
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
|
||||
|
@ -1841,7 +1946,8 @@ page_search(http_connection_t *hc, const char *remain, void *opaque)
|
|||
}
|
||||
|
||||
/* Create autorec rule .. */
|
||||
autorec_create(ar_name, atoi(ar_prio), title, s_ecg, s_tcg, s_ch);
|
||||
autorec_create(ar_name, atoi(ar_prio), title, s_ecg, s_tcg, s_ch,
|
||||
hc->hc_username);
|
||||
|
||||
/* .. and redirect user to video recorder page */
|
||||
http_redirect(hc, "/pvr");
|
||||
|
|
8
main.c
8
main.c
|
@ -56,7 +56,7 @@
|
|||
#include "avgen.h"
|
||||
#include "file_input.h"
|
||||
#include "cwc.h"
|
||||
|
||||
#include "autorec.h"
|
||||
int running;
|
||||
int xmltvreload;
|
||||
int startupcounter;
|
||||
|
@ -181,6 +181,9 @@ main(int argc, char **argv)
|
|||
snprintf(buf, sizeof(buf), "%s/recordings", settings_dir);
|
||||
mkdir(buf, 0777);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/autorec", settings_dir);
|
||||
mkdir(buf, 0777);
|
||||
|
||||
syslog(LOG_NOTICE, "Started HTS TV Headend, settings located in \"%s\"",
|
||||
settings_dir);
|
||||
|
||||
|
@ -200,7 +203,8 @@ main(int argc, char **argv)
|
|||
v4l_init();
|
||||
|
||||
channels_load();
|
||||
|
||||
|
||||
autorec_init();
|
||||
epg_init();
|
||||
xmltv_init();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue