diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index dbbe8f19..bb08b88a 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -83,20 +83,21 @@ extern struct dvr_config_list dvrconfigs; extern struct dvr_entry_list dvrentries; -#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 -#define DVR_WHITESPACE_IN_TITLE 0x20 -#define DVR_DIR_PER_TITLE 0x40 -#define DVR_EPISODE_IN_TITLE 0x80 -#define DVR_CLEAN_TITLE 0x100 -#define DVR_TAG_FILES 0x200 -#define DVR_SKIP_COMMERCIALS 0x400 -#define DVR_SUBTITLE_IN_TITLE 0x800 -#define DVR_EPISODE_BEFORE_DATE 0x1000 -#define DVR_EPISODE_DUPLICATE_DETECTION 0x2000 +#define DVR_FLAGS_VALID 0x1 +#define DVR_DIR_PER_DAY 0x2 +#define DVR_DIR_PER_CHANNEL 0x4 +#define DVR_CHANNEL_IN_TITLE 0x8 +#define DVR_DATE_IN_TITLE 0x10 +#define DVR_TIME_IN_TITLE 0x20 +#define DVR_WHITESPACE_IN_TITLE 0x40 +#define DVR_DIR_PER_TITLE 0x80 +#define DVR_EPISODE_IN_TITLE 0x100 +#define DVR_CLEAN_TITLE 0x200 +#define DVR_TAG_FILES 0x400 +#define DVR_SKIP_COMMERCIALS 0x800 +#define DVR_SUBTITLE_IN_TITLE 0x1000 +#define DVR_EPISODE_BEFORE_DATE 0x2000 +#define DVR_EPISODE_DUPLICATE_DETECTION 0x4000 typedef enum { DVR_PRIO_IMPORTANT, @@ -190,7 +191,6 @@ typedef struct dvr_entry { * Recording state (onyl valid if de_sched_state == DVR_RECORDING) */ dvr_rs_state_t de_rec_state; - int de_locked; /** * Number of errors (only to be modified by the recording thread) @@ -297,6 +297,12 @@ extern const idclass_t dvr_autorec_entry_class; void dvr_make_title(char *output, size_t outlen, dvr_entry_t *de); +static inline int dvr_config_is_valid(dvr_config_t *cfg) + { return !!(cfg->dvr_flags & DVR_FLAGS_VALID); } + +static inline int dvr_config_is_default(dvr_config_t *cfg) + { return cfg->dvr_config_name == NULL || cfg->dvr_config_name[0] == '\0'; } + dvr_config_t *dvr_config_find_by_name(const char *name); dvr_config_t *dvr_config_find_by_name_default(const char *name); @@ -310,7 +316,13 @@ void dvr_config_delete(const char *name); void dvr_config_save(dvr_config_t *cfg); -int dvr_entry_get_mc( dvr_entry_t *de); +static inline int dvr_entry_is_editable(dvr_entry_t *de) + { return de->de_sched_state == DVR_SCHEDULED; } + +static inline int dvr_entry_is_valid(dvr_entry_t *de) + { return de->de_refcnt > 0; } + +int dvr_entry_get_mc(dvr_entry_t *de); void dvr_entry_save(dvr_entry_t *de); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index b0744586..e91a4e23 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -409,7 +409,6 @@ dvr_entry_create(const char *uuid, htsmsg_t *conf) } } - dvr_entry_set_timer(de); htsp_dvr_entry_add(de); @@ -728,7 +727,7 @@ static dvr_entry_t *_dvr_entry_update { int save = 0; - if (de->de_locked) + if (!dvr_entry_is_editable(de)) return de; /* Start/Stop */ @@ -906,7 +905,9 @@ dvr_stop_recording(dvr_entry_t *de, int stopcode, int saveconf) { dvr_config_t *cfg = de->de_config; - if (de->de_rec_state == DVR_RS_PENDING || de->de_rec_state == DVR_RS_WAIT_PROGRAM_START) + if (de->de_rec_state == DVR_RS_PENDING || + de->de_rec_state == DVR_RS_WAIT_PROGRAM_START || + de->de_filename == NULL) de->de_sched_state = DVR_MISSED_TIME; else _dvr_entry_completed(de); @@ -919,10 +920,8 @@ dvr_stop_recording(dvr_entry_t *de, int stopcode, int saveconf) dvr_entry_status(de)); if (saveconf) - idnode_changed(&de->de_id); - else - idnode_notify_simple(&de->de_id); dvr_entry_save(de); + idnode_notify_simple(&de->de_id); htsp_dvr_entry_update(de); gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, @@ -1060,16 +1059,16 @@ dvr_entry_purge(dvr_entry_t *de, int delconf) static void dvr_entry_class_save(idnode_t *self) { - dvr_entry_save((dvr_entry_t *)self); + dvr_entry_t *de = (dvr_entry_t *)self; + dvr_entry_save(de); + if (dvr_entry_is_valid(de)) + dvr_entry_set_timer(de); } static void dvr_entry_class_delete(idnode_t *self) { - dvr_entry_t *de = (dvr_entry_t *)self; - if (de->de_locked) - return; - dvr_entry_destroy(de, 1); + dvr_entry_cancel_delete((dvr_entry_t *)self); } static const char * @@ -1083,13 +1082,66 @@ dvr_entry_class_get_title (idnode_t *self) return s; } +static int +dvr_entry_class_time_set(dvr_entry_t *de, time_t *v, time_t nv) +{ + if (!dvr_entry_is_editable(de)) + return 0; + if (nv != *v) { + *v = nv; + return 1; + } + return 0; +} + +static int +dvr_entry_class_int_set(dvr_entry_t *de, int *v, int nv) +{ + if (!dvr_entry_is_editable(de)) + return 0; + if (nv != *v) { + *v = nv; + return 1; + } + return 0; +} + +static int +dvr_entry_class_start_set(void *o, const void *v) +{ + dvr_entry_t *de = (dvr_entry_t *)o; + return dvr_entry_class_time_set(de, &de->de_start, *(time_t *)v); +} + +static int +dvr_entry_class_start_extra_set(void *o, const void *v) +{ + dvr_entry_t *de = (dvr_entry_t *)o; + return dvr_entry_class_time_set(de, &de->de_start_extra, *(time_t *)v); +} + +static int +dvr_entry_class_stop_set(void *o, const void *_v) +{ + dvr_entry_t *de = (dvr_entry_t *)o; + time_t v = *(time_t *)_v; + + if (!dvr_entry_is_editable(de)) { + if (v < dispatch_clock) + v = dispatch_clock; + } + if (v < de->de_start) + v = de->de_start; + return dvr_entry_class_time_set(de, &de->de_stop, v); +} + static int dvr_entry_class_config_name_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; dvr_config_t *cfg; - if (de->de_locked) + if (!dvr_entry_is_editable(de)) return 0; cfg = v ? dvr_config_find_by_uuid(v) : NULL; if (!cfg) @@ -1128,11 +1180,14 @@ dvr_entry_class_channel_set(void *o, const void *v) dvr_entry_t *de = (dvr_entry_t *)o; channel_t *ch; - if (de->de_locked) + if (!dvr_entry_is_editable(de)) return 0; ch = v ? channel_find_by_uuid(v) : NULL; - if (!de->de_config) + if (!de->de_config) { de->de_config = dvr_config_find_by_name_default(""); + if (de->de_config) + LIST_INSERT_HEAD(&de->de_config->dvr_entries, de, de_config_link); + } if (ch == NULL) { if (de->de_channel) { LIST_REMOVE(de, de_channel_link); @@ -1178,10 +1233,13 @@ dvr_entry_class_channel_name_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; channel_t *ch; - if (de->de_locked) + if (!dvr_entry_is_editable(de)) return 0; - if (!de->de_config) + if (!de->de_config) { de->de_config = dvr_config_find_by_name_default(""); + if (de->de_config) + LIST_INSERT_HEAD(&de->de_config->dvr_entries, de, de_config_link); + } if (!strcmp(de->de_channel_name ?: "", v ?: "")) return 0; ch = v ? channel_find_by_name(v) : NULL; @@ -1206,6 +1264,13 @@ dvr_entry_class_channel_name_get(void *o) return &ret; } +static int +dvr_entry_class_pri_set(void *o, const void *v) +{ + dvr_entry_t *de = (dvr_entry_t *)o; + return dvr_entry_class_int_set(de, &de->de_pri, *(int *)v); +} + htsmsg_t * dvr_entry_class_pri_list ( void *o ) { @@ -1220,6 +1285,13 @@ dvr_entry_class_pri_list ( void *o ) return strtab2htsmsg(tab); } +static int +dvr_entry_class_mc_set(void *o, const void *v) +{ + dvr_entry_t *de = (dvr_entry_t *)o; + return dvr_entry_class_int_set(de, &de->de_mc, *(int *)v); +} + static htsmsg_t * dvr_entry_class_mc_list ( void *o ) { @@ -1254,7 +1326,10 @@ static int dvr_entry_class_autorec_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; - dvr_autorec_entry_t *dae = v ? dvr_autorec_find_by_uuid(v) : NULL; + dvr_autorec_entry_t *dae; + if (!dvr_entry_is_editable(de)) + return 0; + dae = v ? dvr_autorec_find_by_uuid(v) : NULL; if (dae == NULL) { if (de->de_autorec) { LIST_REMOVE(de, de_autorec_link); @@ -1304,7 +1379,10 @@ dvr_entry_class_broadcast_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; uint32_t id = *(uint32_t *)v; - epg_broadcast_t *bcast = epg_broadcast_find_by_id(id, de->de_channel); + epg_broadcast_t *bcast; + if (!dvr_entry_is_editable(de)) + return 0; + bcast = epg_broadcast_find_by_id(id, de->de_channel); if (bcast == NULL) { if (de->de_bcast) { de->de_bcast->putref((epg_object_t*)de->de_bcast); @@ -1563,6 +1641,7 @@ const idclass_t dvr_entry_class = { .type = PT_TIME, .id = "start", .name = "Start Time", + .set = dvr_entry_class_start_set, .off = offsetof(dvr_entry_t, de_start), }, { @@ -1570,6 +1649,7 @@ const idclass_t dvr_entry_class = { .id = "start_extra", .name = "Extra Start Time", .off = offsetof(dvr_entry_t, de_start_extra), + .set = dvr_entry_class_start_extra_set, .list = dvr_entry_class_extra_list, .opts = PO_DURATION, }, @@ -1584,6 +1664,7 @@ const idclass_t dvr_entry_class = { .type = PT_TIME, .id = "stop", .name = "Stop Time", + .set = dvr_entry_class_stop_set, .off = offsetof(dvr_entry_t, de_stop), }, { @@ -1617,7 +1698,7 @@ const idclass_t dvr_entry_class = { .list = dvr_entry_class_channel_list, }, { - .type = PT_TIME, + .type = PT_STR, .id = "channel_icon", .name = "Channel Icon", .get = dvr_entry_class_channel_icon_url_get, @@ -1666,6 +1747,7 @@ const idclass_t dvr_entry_class = { .name = "Priority", .off = offsetof(dvr_entry_t, de_pri), .def.i = DVR_PRIO_NORMAL, + .set = dvr_entry_class_pri_set, .list = dvr_entry_class_pri_list, }, { @@ -1674,6 +1756,7 @@ const idclass_t dvr_entry_class = { .name = "Container", .off = offsetof(dvr_entry_t, de_mc), .def.i = MC_MATROSKA, + .set = dvr_entry_class_mc_set, .list = dvr_entry_class_mc_list, }, { @@ -1852,6 +1935,34 @@ dvr_config_find_by_name_default(const char *name) return cfg; } +/* + * + */ +static void +dvr_config_update_flags(dvr_config_t *cfg) +{ + int r = 0; + if (cfg->dvr_dir_per_day) r |= DVR_DIR_PER_DAY; + if (cfg->dvr_channel_dir) r |= DVR_DIR_PER_CHANNEL; + if (cfg->dvr_channel_in_title) r |= DVR_CHANNEL_IN_TITLE; + if (cfg->dvr_date_in_title) r |= DVR_DATE_IN_TITLE; + if (cfg->dvr_time_in_title) r |= DVR_TIME_IN_TITLE; + if (cfg->dvr_whitespace_in_title) r |= DVR_WHITESPACE_IN_TITLE; + if (cfg->dvr_title_dir) r |= DVR_DIR_PER_TITLE; + if (cfg->dvr_episode_in_title) r |= DVR_EPISODE_IN_TITLE; + if (cfg->dvr_clean_title) r |= DVR_CLEAN_TITLE; + if (cfg->dvr_tag_files) r |= DVR_TAG_FILES; + if (cfg->dvr_skip_commercials) r |= DVR_SKIP_COMMERCIALS; + if (cfg->dvr_subtitle_in_title) r |= DVR_SUBTITLE_IN_TITLE; + if (cfg->dvr_episode_before_date) r |= DVR_EPISODE_BEFORE_DATE; + if (cfg->dvr_episode_duplicate) r |= DVR_EPISODE_DUPLICATE_DETECTION; + cfg->dvr_flags = r | DVR_FLAGS_VALID; + r = 0; + if (cfg->dvr_rewrite_pat) r |= MC_REWRITE_PAT; + if (cfg->dvr_rewrite_pmt) r |= MC_REWRITE_PMT; + cfg->dvr_muxcnf.m_flags = r; +} + /** * create a new named dvr config; the caller is responsible * to avoid duplicates @@ -1903,8 +2014,12 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf) cfg->dvr_muxcnf.m_file_permissions = 0664; cfg->dvr_muxcnf.m_directory_permissions = 0775; - if (conf) + if (conf) { idnode_load(&cfg->dvr_id, conf); + if (dvr_config_is_default(cfg)) + cfg->dvr_enabled = 1; + dvr_config_update_flags(cfg); + } tvhlog(LOG_INFO, "dvr", "Creating new configuration '%s'", cfg->dvr_config_name); @@ -1955,35 +2070,6 @@ dvr_config_delete(const char *name) dvr_config_destroy(cfg, 1); } -/* - * - */ -static void -dvr_config_update_flags(dvr_config_t *cfg) -{ - int r = 0; - if (cfg->dvr_dir_per_day) r |= DVR_DIR_PER_DAY; - if (cfg->dvr_channel_dir) r |= DVR_DIR_PER_CHANNEL; - if (cfg->dvr_channel_in_title) r |= DVR_CHANNEL_IN_TITLE; - if (cfg->dvr_date_in_title) r |= DVR_DATE_IN_TITLE; - if (cfg->dvr_time_in_title) r |= DVR_TIME_IN_TITLE; - if (cfg->dvr_whitespace_in_title) r |= DVR_WHITESPACE_IN_TITLE; - if (cfg->dvr_title_dir) r |= DVR_DIR_PER_TITLE; - if (cfg->dvr_episode_in_title) r |= DVR_EPISODE_IN_TITLE; - if (cfg->dvr_clean_title) r |= DVR_CLEAN_TITLE; - if (cfg->dvr_tag_files) r |= DVR_TAG_FILES; - if (cfg->dvr_skip_commercials) r |= DVR_SKIP_COMMERCIALS; - if (cfg->dvr_subtitle_in_title) r |= DVR_SUBTITLE_IN_TITLE; - if (cfg->dvr_episode_before_date) r |= DVR_EPISODE_BEFORE_DATE; - if (cfg->dvr_episode_duplicate) r |= DVR_EPISODE_DUPLICATE_DETECTION; - cfg->dvr_flags = r; - r = 0; - if (cfg->dvr_rewrite_pat) r |= MC_REWRITE_PAT; - if (cfg->dvr_rewrite_pmt) r |= MC_REWRITE_PMT; - cfg->dvr_muxcnf.m_flags = r; -} - - /* * */ @@ -2006,14 +2092,19 @@ dvr_config_save(dvr_config_t *cfg) static void dvr_config_class_save(idnode_t *self) { - dvr_config_update_flags((dvr_config_t *)self); - dvr_config_save((dvr_config_t *)self); + dvr_config_t *cfg = (dvr_config_t *)self; + if (dvr_config_is_default(cfg)) + cfg->dvr_enabled = 1; + dvr_config_update_flags(cfg); + dvr_config_save(cfg); } static void dvr_config_class_delete(idnode_t *self) { - dvr_config_destroy((dvr_config_t *)self, 1); + dvr_config_t *cfg = (dvr_config_t *)self; + if (!dvr_config_is_default(cfg)) + dvr_config_destroy(cfg, 1); } static int @@ -2042,6 +2133,20 @@ fine: return 0; } +static int +dvr_config_class_enabled_set(void *o, const void *v) +{ + dvr_config_t *cfg = (dvr_config_t *)o; + if (dvr_config_is_default(cfg) && dvr_config_is_valid(cfg)) + return 0; + if (cfg->dvr_enabled != *(int *)v) { + cfg->dvr_enabled = *(int *)v; + return 1; + } + return 0; +} + + static const char * dvr_config_class_get_title (idnode_t *self) { @@ -2119,6 +2224,7 @@ const idclass_t dvr_config_class = { .type = PT_BOOL, .id = "enabled", .name = "Enabled", + .set = dvr_config_class_enabled_set, .off = offsetof(dvr_config_t, dvr_enabled), .def.i = 1, .group = 1, diff --git a/src/dvr/dvr_inotify.c b/src/dvr/dvr_inotify.c index b2226b52..e9aa6441 100644 --- a/src/dvr/dvr_inotify.c +++ b/src/dvr/dvr_inotify.c @@ -115,17 +115,16 @@ void dvr_inotify_add ( dvr_entry_t *de ) SKEL_USED(dvr_inotify_entry_skel); e->path = strdup(e->path); e->fd = inotify_add_watch(_inot_fd, e->path, EVENT_MASK); - if (e->fd == -1) { - tvhlog(LOG_ERR, "dvr", "failed to add inotify watch to %s (err=%s)", - e->path, strerror(errno)); - free(path); - dvr_inotify_del(de); - return; - } } LIST_INSERT_HEAD(&e->entries, de, de_inotify_link); + if (e->fd < 0) { + tvhlog(LOG_ERR, "dvr", "failed to add inotify watch to %s (err=%s)", + e->path, strerror(errno)); + dvr_inotify_del(de); + } + free(path); } @@ -146,7 +145,8 @@ void dvr_inotify_del ( dvr_entry_t *de ) 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); + if (e->fd >= 0) + inotify_rm_watch(_inot_fd, e->fd); free(e->path); free(e); } @@ -180,7 +180,7 @@ _dvr_inotify_find2 snprintf(path, sizeof(path), "%s/%s", die->path, name); LIST_FOREACH(de, &die->entries, de_inotify_link) - if (!strcmp(path, de->de_filename)) + if (de->de_filename && !strcmp(path, de->de_filename)) break; return de; diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 814ea59e..c6d23b1f 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -66,9 +66,6 @@ dvr_rec_subscribe(dvr_entry_t *de) int flags; assert(de->de_s == NULL); - assert(!de->de_locked); - - de->de_locked = 1; if(de->de_pri < 5) weight = prio2weight[de->de_pri]; @@ -120,8 +117,6 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode) globalheaders_destroy(de->de_gh); de->de_last_error = stopcode; - - de->de_locked = 0; } diff --git a/src/idnode.c b/src/idnode.c index 1fa741b2..f22c5f1a 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -473,6 +473,8 @@ idnode_find(const char *uuid, const idclass_t *idc) idnode_t skel, *r; tvhtrace("idnode", "find node %s class %s", uuid, idc ? idc->ic_class : NULL); + if(uuid == NULL || strlen(uuid) != UUID_HEX_SIZE - 1) + return NULL; if(hex2bin(skel.in_uuid, sizeof(skel.in_uuid), uuid)) return NULL; r = RB_FIND(&idnodes, &skel, in_link, in_cmp); diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 2da2cc0b..1a9dd10f 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -83,6 +83,8 @@ tvheadend.dvrRowActions = function() { tvheadend.dvr_upcoming = function(panel, index) { var actions = tvheadend.dvrRowActions(); + var list = 'disp_title,start,start_extra,stop,stop_extra,' + + 'channel,config_name'; tvheadend.idnode_grid(panel, { url: 'api/dvr/entry', @@ -94,11 +96,15 @@ tvheadend.dvr_upcoming = function(panel, index) { add: { url: 'api/dvr/entry', params: { - list: 'disp_title,start,start_extra,stop,stop_extra,' + - 'channel,config_name', + list: list, }, create: { } }, + edit: { + params: { + list: list, + } + }, del: true, list: 'disp_title,episode,pri,start_real,stop_real,' + 'duration,channelname,creator,config_name,' +