From: Adam Sutton Date: Thu, 10 Jan 2013 17:04:31 +0000 (+0000) Subject: Fix #1083 - add inotify monitoring of recordings. X-Git-Tag: v3.5~124 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=accc01db56b50cdf303d382dd8647323c704e262;p=thirdparty%2Ftvheadend.git Fix #1083 - add inotify monitoring of recordings. 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. --- diff --git a/Makefile b/Makefile index 6f4e2ab32..b337e7dc2 100644 --- 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 \ diff --git a/configure b/configure index 870f2a4ad..267187abf 100755 --- 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 diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 1ec73fb2d..0e56c4681 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -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 */ diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index d975b1f58..6ab51aca0 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -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 index 000000000..22c6d22b1 --- /dev/null +++ b/src/dvr/dvr_inotify.c @@ -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 . + */ + +#include +#include +#include +#include +#include +#include + +#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; +} +