]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Fix #1083 - add inotify monitoring of recordings.
authorAdam Sutton <dev@adamsutton.me.uk>
Thu, 10 Jan 2013 17:04:31 +0000 (17:04 +0000)
committerAdam Sutton <dev@adamsutton.me.uk>
Fri, 11 Jan 2013 11:13:29 +0000 (11:13 +0000)
Should a recording be moved (within same dir) the DVR DB will be
updated.

Should it be moved (out of directory) or deleted entirely, it will
simply be marked as missing.

The parent directories are also monitored should they be moved
or deleted.

Makefile
configure
src/dvr/dvr.h
src/dvr/dvr_db.c
src/dvr/dvr_inotify.c [new file with mode: 0644]

index 6f4e2ab32b29dd1cff3e46f0bf21c6e0fcaad9b1..b337e7dc2c48947112a53bb5ed3041b9493fec6e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -170,6 +170,10 @@ SRCS-${CONFIG_LINUXDVB} += \
        src/webui/extjs_dvb.c \
        src/muxes.c \
 
+# Inotify
+SRCS-${CONFIG_INOTIFY} += \
+  src/dvr/dvr_inotify.c \
+
 # V4L
 SRCS-${CONFIG_V4L} += \
        src/v4l.c \
index 870f2a4ad66cee5dc60c4305153ccc89f2c1aaed..267187abfad085ac37d1a296e301a4e184082c04 100755 (executable)
--- a/configure
+++ b/configure
@@ -25,6 +25,7 @@ OPTIONS=(
   "avahi:auto"
   "zlib:auto"
   "libav:auto"
+  "inotify:auto"
   "bundle:no"
   "dvbcsa:no"
 )
@@ -135,6 +136,16 @@ if enabled_or_auto libav; then
   fi
 fi
 
+#
+# Inotify
+#
+if enabled_or_auto inotify; then
+  if check_cc_header "sys/inotify" inotify_h; then
+    enable inotify
+  elif enabled inotify; then
+    die "Inotify support not found (use --disable-inotify)"
+  fi
+fi
 
 #
 # DVB scan
index 1ec73fb2d7bc1a9194f58de09a9dd806815dce3d..0e56c46812ab278e32b7ad6c1e2d82c6f97b0f40 100644 (file)
@@ -189,6 +189,13 @@ typedef struct dvr_entry {
 
   struct muxer *de_mux;
 
+  /**
+   * Inotify
+   */
+#if ENABLE_INOTIFY
+  LIST_ENTRY(dvr_entry) de_inotify_link;
+#endif
+
 } dvr_entry_t;
 
 
@@ -378,4 +385,11 @@ dvr_prio_t dvr_pri2val(const char *s);
 
 const char *dvr_val2pri(dvr_prio_t v);
 
+/**
+ * Inotify support
+ */
+void dvr_inotify_init ( void );
+void dvr_inotify_add  ( dvr_entry_t *de );
+void dvr_inotify_del  ( dvr_entry_t *de );
+
 #endif /* DVR_H  */
index d975b1f58b5d1ac0cf8a4bd2036a45ea463f146f..6ab51aca036384416b3e1497565aa465a1d6354b 100644 (file)
@@ -40,6 +40,18 @@ struct dvr_entry_list dvrentries;
 static void dvr_timer_expire(void *aux);
 static void dvr_timer_start_recording(void *aux);
 
+/*
+ * Completed
+ */
+static void
+_dvr_entry_completed(dvr_entry_t *de)
+{
+  de->de_sched_state = DVR_COMPLETED;
+#if ENABLE_INOTIFY
+  dvr_inotify_add(de);
+#endif
+}
+
 /**
  * Return printable status for a dvr entry
  */
@@ -224,7 +236,7 @@ dvr_entry_link(dvr_entry_t *de)
     if(de->de_filename == NULL)
       de->de_sched_state = DVR_MISSED_TIME;
     else
-      de->de_sched_state = DVR_COMPLETED;
+      _dvr_entry_completed(de);
     gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, 
               de->de_stop + cfg->dvr_retention_days * 86400);
 
@@ -436,6 +448,10 @@ dvr_entry_remove(dvr_entry_t *de)
   hts_settings_remove("dvr/log/%d", de->de_id);
 
   htsp_dvr_entry_delete(de);
+  
+#if ENABLE_INOTIFY
+  dvr_inotify_del(de);
+#endif
 
   gtimer_disarm(&de->de_timer);
 
@@ -759,7 +775,7 @@ dvr_stop_recording(dvr_entry_t *de, int stopcode)
   if (de->de_rec_state == DVR_RS_PENDING || de->de_rec_state == DVR_RS_WAIT_PROGRAM_START)
     de->de_sched_state = DVR_MISSED_TIME;
   else
-    de->de_sched_state = DVR_COMPLETED;
+    _dvr_entry_completed(de);
 
   dvr_rec_unsubscribe(de, stopcode);
 
@@ -1037,6 +1053,7 @@ dvr_init(void)
     }
   }
 
+  dvr_inotify_init();
   dvr_autorec_init();
   dvr_db_load();
   dvr_autorec_update();
@@ -1424,6 +1441,9 @@ void
 dvr_entry_delete(dvr_entry_t *de)
 {
   if(de->de_filename != NULL) {
+#if ENABLE_INOTIFY
+    dvr_inotify_del(de);
+#endif
     if(unlink(de->de_filename) && errno != ENOENT)
       tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s",
             de->de_filename, strerror(errno));
diff --git a/src/dvr/dvr_inotify.c b/src/dvr/dvr_inotify.c
new file mode 100644 (file)
index 0000000..22c6d22
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ *  Digital Video Recorder - inotify processing
+ *  Copyright (C) 2012 Adam Sutton
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+
+#include "tvheadend.h"
+#include "redblack.h"
+#include "dvr/dvr.h"
+#include "htsp_server.h"
+
+/* inotify limits */
+#define EVENT_SIZE    ( sizeof (struct inotify_event) )
+#define EVENT_BUF_LEN ( 10 * ( EVENT_SIZE + 16 ) )
+#define EVENT_MASK    IN_CREATE    | IN_DELETE     | IN_DELETE_SELF |\
+                      IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO
+                      
+static int                         _inot_fd;
+static RB_HEAD(,dvr_inotify_entry) _inot_tree;
+
+typedef struct dvr_inotify_entry
+{
+  RB_ENTRY(dvr_inotify_entry) link;
+  char                        *path;
+  int                          fd;
+  struct dvr_entry_list        entries;
+} dvr_inotify_entry_t;
+
+static void* _dvr_inotify_thread ( void *p );
+
+static int _str_cmp ( void *a, void *b )
+{
+  return strcmp(((dvr_inotify_entry_t*)a)->path, ((dvr_inotify_entry_t*)b)->path);
+}
+
+/**
+ * Initialise threads
+ */
+void dvr_inotify_init ( void )
+{
+  pthread_t tid;
+
+  _inot_fd = inotify_init();
+
+  pthread_create(&tid, NULL, _dvr_inotify_thread, NULL);
+}
+
+/**
+ * Add an entry for monitoring
+ */
+void dvr_inotify_add ( dvr_entry_t *de )
+{
+  static dvr_inotify_entry_t *skel = NULL;
+  dvr_inotify_entry_t *e;
+  char *path;
+  struct stat st;
+
+  if (!de->de_filename || stat(de->de_filename, &st))
+    return;
+
+  path = strdup(de->de_filename);
+
+  if (!skel)
+    skel = calloc(1, sizeof(dvr_inotify_entry_t));
+  skel->path = dirname(path);
+  
+  if (stat(skel->path, &st))
+    return;
+  
+  e = RB_INSERT_SORTED(&_inot_tree, skel, link, _str_cmp);
+  if (!e) {
+    e       = skel;
+    skel    = NULL;
+    e->path = strdup(e->path);
+    e->fd   = inotify_add_watch(_inot_fd, e->path, EVENT_MASK);
+    assert(e->fd != -1);
+  }
+
+  LIST_INSERT_HEAD(&e->entries, de, de_inotify_link);
+
+  free(path);
+}
+
+/*
+ * Delete an entry from the monitor
+ */
+void dvr_inotify_del ( dvr_entry_t *de )
+{
+  dvr_entry_t *det;
+  dvr_inotify_entry_t *e;
+  RB_FOREACH(e, &_inot_tree, link) {
+    LIST_FOREACH(det, &e->entries, de_inotify_link)
+      if (det == de) break;
+    if (det) break;
+  }
+
+  if (e && det) {
+    LIST_REMOVE(det, de_inotify_link);
+    if (LIST_FIRST(&e->entries) == NULL) {
+      RB_REMOVE(&_inot_tree, e, link);
+      inotify_rm_watch(_inot_fd, e->fd);
+      free(e->path);
+      free(e);
+    }
+  }
+}
+
+/*
+ * Find inotify entry
+ */
+static dvr_inotify_entry_t *
+_dvr_inotify_find
+  ( int fd )
+{
+  dvr_inotify_entry_t *e = NULL;
+  RB_FOREACH(e, &_inot_tree, link)
+    if (e->fd == fd)
+      break;
+  return e;
+}
+
+/*
+ * Find DVR entry
+ */
+static dvr_entry_t *
+_dvr_inotify_find2
+  ( dvr_inotify_entry_t *die, const char *name )
+{
+  dvr_entry_t *de = NULL;
+  char path[512];
+  
+  snprintf(path, sizeof(path), "%s/%s", die->path, name);
+  
+  LIST_FOREACH(de, &die->entries, de_inotify_link)
+    if (!strcmp(path, de->de_filename))
+      break;
+  
+  return de;
+}
+
+/*
+ * File moved
+ */
+static void
+_dvr_inotify_moved
+  ( int fd, const char *from, const char *to )
+{
+  dvr_inotify_entry_t *die;
+  dvr_entry_t *de;
+
+  if (!(die = _dvr_inotify_find(fd)))
+    return;
+
+  if (!(de = _dvr_inotify_find2(die, from)))
+    return;
+
+  if (to) {
+    char path[512];
+    snprintf(path, sizeof(path), "%s/%s", die->path, to);
+    tvh_str_update(&de->de_filename, path);
+    dvr_entry_save(de);
+  } else
+    dvr_inotify_del(de);
+  
+  htsp_dvr_entry_update(de);
+  dvr_entry_notify(de);
+}
+
+/*
+ * File deleted
+ */
+static void
+_dvr_inotify_delete
+  ( int fd, const char *path )
+{
+  _dvr_inotify_moved(fd, path, NULL);
+}
+
+/*
+ * Directory moved
+ */
+static void
+_dvr_inotify_moved_all
+  ( int fd, const char *to )
+{
+  dvr_entry_t *de;
+  dvr_inotify_entry_t *die;
+  
+  if (!(die = _dvr_inotify_find(fd)))
+    return;
+
+  while ((de = LIST_FIRST(&die->entries))) {
+    htsp_dvr_entry_update(de);
+    dvr_entry_notify(de);
+    dvr_inotify_del(de);
+  }
+}
+
+/*
+ * Directory deleted
+ */
+static void
+_dvr_inotify_delete_all
+  ( int fd )
+{
+  _dvr_inotify_moved_all(fd, NULL);
+}
+
+/*
+ * Process events
+ */
+void* _dvr_inotify_thread ( void *p )
+{
+  int i, len;
+  char buf[EVENT_BUF_LEN];
+  const char *from;
+  int fromfd;
+  int cookie;
+
+  while (1) {
+
+    /* Read events */
+    fromfd = 0;
+    cookie = 0;
+    from   = NULL;
+    i      = 0;
+    len    = read(_inot_fd, buf, EVENT_BUF_LEN);
+
+    /* Process */
+    pthread_mutex_lock(&global_lock);
+    while ( i < len ) {
+      struct inotify_event *ev = (struct inotify_event*)&buf[i];
+      i += EVENT_SIZE + ev->len;
+
+      /* Moved */
+      if (ev->mask & IN_MOVED_FROM) {
+        from   = ev->name;
+        fromfd = ev->wd;
+        cookie = ev->cookie;
+        continue;
+
+      } else if ((ev->mask & IN_MOVED_TO) && from && ev->cookie == cookie) {
+        _dvr_inotify_moved(ev->wd, from, ev->name);
+        from = NULL;
+      
+      /* Removed */
+      } else if (ev->mask & IN_DELETE) {
+        _dvr_inotify_delete(ev->wd, ev->name);
+    
+      /* Moved self */
+      } else if (ev->mask & IN_MOVE_SELF) {
+        _dvr_inotify_moved_all(ev->wd, NULL);
+    
+      /* Removed self */
+      } else if (ev->mask & IN_DELETE_SELF) {
+        _dvr_inotify_delete_all(ev->wd);
+      }
+
+      if (from) {
+        _dvr_inotify_moved(fromfd, from, NULL);
+        from   = NULL;
+        cookie = 0;
+      }
+    }
+    if (from)
+      _dvr_inotify_moved(fromfd, from, NULL);
+    pthread_mutex_unlock(&global_lock);
+  }
+
+  return NULL;
+}
+