]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Make root directory, log retention time and DVR titles/filenames configurable.
authorAndreas Öman <andreas@lonelycoder.com>
Sun, 21 Sep 2008 09:37:35 +0000 (09:37 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Sun, 21 Sep 2008 09:37:35 +0000 (09:37 +0000)
dvr/dvr.h
dvr/dvr_db.c
dvr/dvr_rec.c
webui/extjs.c
webui/static/app/dvr.js

index c32462e83b230ed303b2d2058febcc02d36d3605..948b28cc587b611d359c6f336e3f4f5275b2f387 100644 (file)
--- 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
  */
index 6c21be941d590721c670b5be3450603050d66e94..032c7a90a24f4db1614022667c704f6364addd06 100644 (file)
@@ -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();
+}
+
+
 /**
  *
  */
index d8c5388c2263e8f58a4588ea2198d9aa7c234ca0..32cc5886bf8e4cb4023ee54bb1f0aab7fdd24e41 100644 (file)
@@ -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;
+
+  filename = strdup(de->de_ititle);
+  deslashify(filename);
+
+  av_strlcpy(path, dvr_storage, sizeof(path));
+
+  /* Append per-day directory */
 
-  if(de->de_filename != NULL) {
-    free(de->de_filename);
-    de->de_filename = NULL;
+  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);
   }
 
-  filename = strdup(de->de_title);
-  deslashify(filename);
+  /* 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);
+  }
 
-  chname = strdup(de->de_channel->ch_name);
-  deslashify(chname);
+  /* */
+  if(makedirs(path) != 0)
+    return -1;
+  
 
-  snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s",
-          dvr_storage, chname, filename, dvr_file_postfix);
+  /* 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;
index b4cf40b2d92c77cd6446e4e163edf7f26e7e1719..7b09a3ea30e5c0370dc8bb8806386bedb1f8a965 100644 (file)
@@ -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);
index 3ba774b9ed6f800543f5b8418509782e8257ca4d..b6ab060e7dfc9535a0cb8bd89691abf45d2663c2 100644 (file)
@@ -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',