From fb33332ebdfd5a54077a1e71db8888a3059458ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Sun, 21 Sep 2008 09:37:35 +0000 Subject: [PATCH] Make root directory, log retention time and DVR titles/filenames configurable. --- dvr/dvr.h | 11 ++++ dvr/dvr_db.c | 77 ++++++++++++++++++++++++ dvr/dvr_rec.c | 126 +++++++++++++++++++++++++++++++--------- webui/extjs.c | 19 ++++++ webui/static/app/dvr.js | 24 ++++++-- 5 files changed, 227 insertions(+), 30 deletions(-) diff --git a/dvr/dvr.h b/dvr/dvr.h index c32462e8..948b28cc 100644 --- a/dvr/dvr.h +++ b/dvr/dvr.h @@ -28,6 +28,13 @@ extern char *dvr_storage; extern char *dvr_format; extern char *dvr_file_postfix; extern uint32_t dvr_retention_days; +extern int dvr_flags; + +#define DVR_DIR_PER_DAY 0x1 +#define DVR_DIR_PER_CHANNEL 0x2 +#define DVR_CHANNEL_IN_TITLE 0x4 +#define DVR_DATE_IN_TITLE 0x8 +#define DVR_TIME_IN_TITLE 0x10 LIST_HEAD(dvr_rec_stream_list, dvr_rec_stream); @@ -71,6 +78,8 @@ typedef struct dvr_entry { char *de_filename; /* Initially null if no filename has been generated yet */ char *de_title; /* Title in UTF-8 (from EPG) */ + char *de_ititle; /* Internal title optionally with channelname + date and time pre/post/fixed */ char *de_desc; /* Description in UTF-8 (from EPG) */ char *de_error; @@ -131,6 +140,8 @@ void dvr_storage_set(const char *storage); void dvr_retention_set(int days); +void dvr_flags_set(int flags); + /** * Query interface */ diff --git a/dvr/dvr_db.c b/dvr/dvr_db.c index 6c21be94..032c7a90 100644 --- a/dvr/dvr_db.c +++ b/dvr/dvr_db.c @@ -32,6 +32,7 @@ char *dvr_storage; char *dvr_format; char *dvr_file_postfix; uint32_t dvr_retention_days; +int dvr_flags; static int de_tally; @@ -58,6 +59,39 @@ dvrdb_changed(void) } + +/** + * + */ +static void +dvr_make_title(char *output, size_t outlen, const char *title, + const char *channel, time_t starttime) +{ + struct tm tm; + char buf[40]; + + if(dvr_flags & DVR_CHANNEL_IN_TITLE) + snprintf(output, outlen, "%s-", channel); + else + output[0] = 0; + + snprintf(output + strlen(output), outlen - strlen(output), + "%s", title); + + localtime_r(&starttime, &tm); + + if(dvr_flags & DVR_DATE_IN_TITLE) { + strftime(buf, sizeof(buf), "%F", &tm); + snprintf(output + strlen(output), outlen - strlen(output), "-%s", buf); + } + + if(dvr_flags & DVR_TIME_IN_TITLE) { + strftime(buf, sizeof(buf), "%R", &tm); + snprintf(output + strlen(output), outlen - strlen(output), "-%s", buf); + } +} + + /** * */ @@ -65,6 +99,12 @@ static void dvr_entry_link(dvr_entry_t *de) { time_t now, preamble; + char buf[100]; + + dvr_make_title(buf, sizeof(buf), de->de_title, de->de_channel->ch_name, + de->de_start); + + de->de_ititle = strdup(buf); de->de_refcnt = 1; @@ -87,6 +127,7 @@ dvr_entry_link(dvr_entry_t *de) } + /** * */ @@ -146,6 +187,7 @@ dvr_entry_dec_ref(dvr_entry_t *de) free(de->de_creator); free(de->de_title); + free(de->de_ititle); free(de->de_desc); free(de); @@ -393,6 +435,7 @@ dvr_init(void) char buf[500]; const char *homedir; struct stat st; + uint32_t u32; /* Default settings */ @@ -406,6 +449,21 @@ dvr_init(void) htsmsg_get_u32(m, "retention-days", &dvr_retention_days); tvh_str_set(&dvr_storage, htsmsg_get_str(m, "storage")); + if(!htsmsg_get_u32(m, "day-dir", &u32) && u32) + dvr_flags |= DVR_DIR_PER_DAY; + + if(!htsmsg_get_u32(m, "channel-dir", &u32) && u32) + dvr_flags |= DVR_DIR_PER_CHANNEL; + + if(!htsmsg_get_u32(m, "channel-in-title", &u32) && u32) + dvr_flags |= DVR_CHANNEL_IN_TITLE; + + if(!htsmsg_get_u32(m, "date-in-title", &u32) && u32) + dvr_flags |= DVR_DATE_IN_TITLE; + + if(!htsmsg_get_u32(m, "time-in-title", &u32) && u32) + dvr_flags |= DVR_TIME_IN_TITLE; + htsmsg_destroy(m); } @@ -447,6 +505,11 @@ dvr_save(void) htsmsg_t *m = htsmsg_create(); htsmsg_add_str(m, "storage", dvr_storage); htsmsg_add_u32(m, "retention-days", dvr_retention_days); + htsmsg_add_u32(m, "day-dir", !!(dvr_flags & DVR_DIR_PER_DAY)); + htsmsg_add_u32(m, "channel-dir", !!(dvr_flags & DVR_DIR_PER_CHANNEL)); + htsmsg_add_u32(m, "channel-in-title", !!(dvr_flags & DVR_CHANNEL_IN_TITLE)); + htsmsg_add_u32(m, "date-in-title", !!(dvr_flags & DVR_DATE_IN_TITLE)); + htsmsg_add_u32(m, "time-in-title", !!(dvr_flags & DVR_TIME_IN_TITLE)); hts_settings_save(m, "dvr"); htsmsg_destroy(m); @@ -488,6 +551,20 @@ dvr_retention_set(int days) } +/** + * + */ +void +dvr_flags_set(int flags) +{ + if(dvr_flags == flags) + return; + + dvr_flags = flags; + dvr_save(); +} + + /** * */ diff --git a/dvr/dvr_rec.c b/dvr/dvr_rec.c index d8c5388c..32cc5886 100644 --- a/dvr/dvr_rec.c +++ b/dvr/dvr_rec.c @@ -135,6 +135,53 @@ dvr_rec_unsubscribe(dvr_entry_t *de) } +/** + * + */ +static int +makedirs(const char *path) +{ + struct stat st; + char *p; + int l, r; + + if(stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + return 0; /* Dir already there */ + + if(mkdir(path, 0777) == 0) + return 0; /* Dir created ok */ + + if(errno == ENOENT) { + + /* Parent does not exist, try to create it */ + /* Allocate new path buffer and strip off last directory component */ + + l = strlen(path); + p = alloca(l + 1); + memcpy(p, path, l); + p[l--] = 0; + + for(; l >= 0; l--) + if(p[l] == '/') + break; + if(l == 0) { + return ENOENT; + } + p[l] = 0; + + if((r = makedirs(p)) != 0) + return r; + + /* Try again */ + if(mkdir(path, 0777) == 0) + return 0; /* Dir created ok */ + } + r = errno; + + tvhlog(LOG_DEBUG, "dvr", "Unable to create directory \"%s\" -- %s", + path, strerror(r)); + return r; +} /** @@ -155,28 +202,52 @@ deslashify(char *s) * - avoid duplicate filenames * */ -static void +static int pvr_generate_filename(dvr_entry_t *de) { char fullname[1000]; + char path[500]; int tally = 0; struct stat st; - char *chname; char *filename; + char *chname; + struct tm tm; - if(de->de_filename != NULL) { - free(de->de_filename); - de->de_filename = NULL; - } - - filename = strdup(de->de_title); + filename = strdup(de->de_ititle); deslashify(filename); - chname = strdup(de->de_channel->ch_name); - deslashify(chname); + av_strlcpy(path, dvr_storage, sizeof(path)); - snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s", - dvr_storage, chname, filename, dvr_file_postfix); + /* Append per-day directory */ + + if(dvr_flags & DVR_DIR_PER_DAY) { + localtime_r(&de->de_start, &tm); + strftime(fullname, sizeof(fullname), "%F", &tm); + deslashify(fullname); + snprintf(path + strlen(path), sizeof(path) - strlen(path), + "/%s", fullname); + } + + /* Append per-channel directory */ + + if(dvr_flags & DVR_DIR_PER_CHANNEL) { + + chname = strdup(de->de_channel->ch_name); + deslashify(chname); + snprintf(path + strlen(path), sizeof(path) - strlen(path), + "/%s", chname); + free(chname); + } + + /* */ + if(makedirs(path) != 0) + return -1; + + + /* Construct final name */ + + snprintf(fullname, sizeof(fullname), "%s/%s.%s", + path, filename, dvr_file_postfix); while(1) { if(stat(fullname, &st) == -1) { @@ -190,14 +261,14 @@ pvr_generate_filename(dvr_entry_t *de) tally++; - snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s", - dvr_storage, chname, filename, tally,dvr_file_postfix); + snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s", + path, filename, tally, dvr_file_postfix); } - de->de_filename = strdup(fullname); + tvh_str_set(&de->de_filename, fullname); free(filename); - free(chname); + return 0; } /** @@ -230,7 +301,10 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp) pthread_t ptid; pthread_attr_t attr; - pvr_generate_filename(de); + if(pvr_generate_filename(de) != 0) { + dvr_rec_fatal_error(de, "Unable to create directories"); + return; + } /* Find lavf format */ @@ -245,7 +319,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp) fctx = av_alloc_format_context(); - av_strlcpy(fctx->title, de->de_title ?: "", + av_strlcpy(fctx->title, de->de_ititle ?: "", sizeof(fctx->title)); av_strlcpy(fctx->comment, de->de_desc ?: "", @@ -309,7 +383,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp) if(codec == NULL) { tvhlog(LOG_ERR, "dvr", "%s - Cannot find codec for %s, ignoring stream", - de->de_title, codec_name); + de->de_ititle, codec_name); continue; } @@ -325,7 +399,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp) if(avcodec_open(ctx, codec) < 0) { tvhlog(LOG_ERR, "dvr", "%s - Cannot open codec for %s, ignoring stream", - de->de_title, codec_name); + de->de_ititle, codec_name); free(ctx); continue; } @@ -503,7 +577,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) tvhlog(LOG_DEBUG, "dvr", "%s - " "Stream #%d: \"%s\" decoded a complete audio frame: " "%d channels in %d Hz", - de->de_title, st->index, ctx->codec->name, + de->de_ititle, st->index, ctx->codec->name, ctx->channels, ctx->sample_rate); drs->drs_decoded = 1; @@ -522,7 +596,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) tvhlog(LOG_DEBUG, "dvr", "%s - " "Stream #%d: \"%s\" decoded a complete video frame: " "%d x %d at %.2fHz", - de->de_title, st->index, ctx->codec->name, + de->de_ititle, st->index, ctx->codec->name, ctx->width, st->codec->height, (float)ctx->time_base.den / (float)ctx->time_base.num); @@ -541,7 +615,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) if(av_write_header(fctx)) { tvhlog(LOG_ERR, "dvr", "%s - Unable to write header", - de->de_title); + de->de_ititle); break; } @@ -549,7 +623,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) tvhlog(LOG_ERR, "dvr", "%s - Header written to file, stream dump:", - de->de_title); + de->de_ititle); for(i = 0; i < fctx->nb_streams; i++) { stx = fctx->streams[i]; @@ -557,7 +631,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) avcodec_string(txt, sizeof(txt), stx->codec, 1); tvhlog(LOG_ERR, "dvr", "%s - Stream #%d: %s [%d/%d]", - de->de_title, i, txt, + de->de_ititle, i, txt, stx->time_base.num, stx->time_base.den); } @@ -610,7 +684,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) de->de_rec_state = DE_RS_RUNNING; tvhlog(LOG_ERR, "dvr", "%s - Skipped %lld seconds of commercials", - de->de_title, (pkt->pkt_dts - de->de_ts_com_start) / 1000000); + de->de_ititle, (pkt->pkt_dts - de->de_ts_com_start) / 1000000); goto outputpacket; } break; diff --git a/webui/extjs.c b/webui/extjs.c index b4cf40b2..7b09a3ea 100644 --- a/webui/extjs.c +++ b/webui/extjs.c @@ -891,6 +891,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) event_t *e; dvr_entry_t *de; const char *s; + int flags = 0; if(op == NULL) op = "loadSettings"; @@ -927,6 +928,12 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) r = htsmsg_create(); htsmsg_add_str(r, "storage", dvr_storage); htsmsg_add_u32(r, "retention", dvr_retention_days); + htsmsg_add_u32(r, "dayDirs", !!(dvr_flags & DVR_DIR_PER_DAY)); + htsmsg_add_u32(r, "channelDirs", !!(dvr_flags & DVR_DIR_PER_CHANNEL)); + htsmsg_add_u32(r, "channelInTitle", !!(dvr_flags & DVR_CHANNEL_IN_TITLE)); + htsmsg_add_u32(r, "dateInTitle", !!(dvr_flags & DVR_DATE_IN_TITLE)); + htsmsg_add_u32(r, "timeInTitle", !!(dvr_flags & DVR_TIME_IN_TITLE)); + out = json_single_record(r, "dvrSettings"); } else if(!strcmp(op, "saveSettings")) { @@ -937,6 +944,18 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) if((s = http_arg_get(&hc->hc_req_args, "retention")) != NULL) dvr_retention_set(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, "dateInTitle") != NULL) + flags |= DVR_DATE_IN_TITLE; + if(http_arg_get(&hc->hc_req_args, "timeInTitle") != NULL) + flags |= DVR_TIME_IN_TITLE; + + dvr_flags_set(flags); out = htsmsg_create(); htsmsg_add_u32(out, "success", 1); diff --git a/webui/static/app/dvr.js b/webui/static/app/dvr.js index 3ba774b9..b6ab060e 100644 --- a/webui/static/app/dvr.js +++ b/webui/static/app/dvr.js @@ -197,8 +197,9 @@ tvheadend.dvrsettings = function() { var confreader = new Ext.data.JsonReader({ root: 'dvrSettings', - }, ['storage','retention']); - + }, ['storage','retention','dayDirs', + 'channelDirs','channelInTitle', + 'dateInTitle','timeInTitle']); var confpanel = new Ext.FormPanel({ title:'Digital Video Recorder', @@ -206,7 +207,7 @@ tvheadend.dvrsettings = function() { bodyStyle:'padding:15px', anchor: '100% 50%', labelAlign: 'right', - labelWidth: 150, + labelWidth: 200, waitMsgTarget: true, reader: confreader, defaultType: 'textfield', @@ -219,8 +220,23 @@ tvheadend.dvrsettings = function() { allowNegative: false, allowDecimals: false, minValue: 1, - fieldLabel: 'Retention time (days)', + fieldLabel: 'DVR Log retention time (days)', name: 'retention' + }), new Ext.form.Checkbox({ + fieldLabel: 'Make subdirectories per day', + name: 'dayDirs' + }), new Ext.form.Checkbox({ + fieldLabel: 'Make subdirectories per channel', + name: 'channelDirs' + }), new Ext.form.Checkbox({ + fieldLabel: 'Include channel name in title', + name: 'channelInTitle' + }), new Ext.form.Checkbox({ + fieldLabel: 'Include date in title', + name: 'dateInTitle' + }), new Ext.form.Checkbox({ + fieldLabel: 'Include time in title', + name: 'timeInTitle' })], tbar: [{ tooltip: 'Save changes made to channel configuration below',