Add support for autorecording rules

This commit is contained in:
Andreas Öman 2008-02-13 21:38:41 +00:00
parent f043211140
commit dc86a566fa
5 changed files with 449 additions and 21 deletions

283
autorec.c
View file

@ -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);
}

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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();