From dc86a566fa139eab5c51d450f4128af061e20bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Wed, 13 Feb 2008 21:38:41 +0000 Subject: [PATCH] Add support for autorecording rules --- autorec.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++- autorec.h | 37 ++++++- epg.c | 12 +++ htmlui.c | 130 ++++++++++++++++++++++--- main.c | 8 +- 5 files changed, 449 insertions(+), 21 deletions(-) diff --git a/autorec.c b/autorec.c index 525c70a8..53205aaf 100644 --- a/autorec.c +++ b/autorec.c @@ -25,9 +25,8 @@ #include #include #include -#include #include - +#include #include #include #include @@ -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); } diff --git a/autorec.h b/autorec.h index 852deda9..1afc586d 100644 --- a/autorec.h +++ b/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 + +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 */ diff --git a/epg.c b/epg.c index 1cf6ba22..5780fbaa 100644 --- a/epg.c +++ b/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 diff --git a/htmlui.c b/htmlui.c index ee943709..a9579c05 100644 --- a/htmlui.c +++ b/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, "
"); - box_top(&tq, "box"); + /* + * PVR log + */ + box_top(&tq, "box"); tcp_qprintf(&tq, "
%s
", "Recorder Log"); - tcp_qprintf(&tq, "
"); c = 0; @@ -981,6 +992,14 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque) ""); + tcp_qprintf(&tq, + "" + "" + "", + pvrr->pvrr_creator ?: "not set"); + tcp_qprintf(&tq, "" "", val2str(pvrr->pvrr_rec_status, recintstatustxt) ?: "invalid"); - tcp_qprintf(&tq, - "" - "" - "", - pvrr->pvrr_creator ?: "not set"); - tcp_qprintf(&tq, "
" + "Created by:" + "%s
" @@ -997,14 +1016,6 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque) "
" - "Created by:" - "%s
"); @@ -1042,6 +1053,100 @@ page_pvr(http_connection_t *hc, const char *remain, void *opaque) tcp_qprintf(&tq, "
\r\n"); box_bottom(&tq); + tcp_qprintf(&tq, "
\r\n"); + + /* + * Autorecorder + */ + + box_top(&tq, "box"); + tcp_qprintf(&tq, + "
%s
", + "Autorecorder ruleset"); + tcp_qprintf(&tq, "
\r\n"); + + if(TAILQ_FIRST(&autorecs) == NULL) + tcp_qprintf(&tq, "
No entries

"); + + TAILQ_FOREACH(ar, &autorecs, ar_link) { + tcp_qprintf(&tq, + "
\r\n", + divid, ar->ar_name); + + tcp_qprintf(&tq, + "
", + divid, ar_show == ar ? "block" : "none"); + + tcp_qprintf(&tq, + ""); + + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_creator ?: "not set"); + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_rec_prio); + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_title ?: "All"); + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_ecg ? ar->ar_ecg->ecg_name: "All"); + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_tcg ? ar->ar_tcg->tcg_name: "All"); + + tcp_qprintf(&tq, + "" + "" + "", + ar->ar_ch ? ar->ar_ch->ch_name: "All"); + + tcp_qprintf(&tq, + "
" + "Created by:" + "%s
" + "Recording priority:" + "%d
" + "Event tile:" + "%s
" + "Content group:" + "%s
" + "Channel group:" + "%s
" + "Channel:" + "%s
"); + + tcp_qprintf(&tq,"
" + "
", ar->ar_id); + + tcp_qprintf(&tq, "
\n"); + + + divid++; + } + tcp_qprintf(&tq, "
\r\n"); + box_bottom(&tq); + tcp_qprintf(&tq, "
\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"); diff --git a/main.c b/main.c index a9c71ba3..2ab45e05 100644 --- a/main.c +++ b/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();