From: Glenn-1990 Date: Wed, 2 Dec 2015 20:35:53 +0000 (+0100) Subject: DVR: implement "keep forever" and "keep until space needed" X-Git-Tag: v4.2.1~1399 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f9c0c6a50903e11521b49649851906b6348b9eb8;p=thirdparty%2Ftvheadend.git DVR: implement "keep forever" and "keep until space needed" --- diff --git a/src/api/api_dvr.c b/src/api/api_dvr.c index bfd2aef82..3ee87f0a3 100644 --- a/src/api/api_dvr.c +++ b/src/api/api_dvr.c @@ -240,7 +240,7 @@ api_dvr_entry_create_by_event e, 0, 0, perm->aa_username, perm->aa_representative, - NULL, DVR_PRIO_NORMAL, 0, 0, comment); + NULL, DVR_PRIO_NORMAL, DVR_RET_DVRCONFIG, DVR_RET_DVRCONFIG, comment); if (de) dvr_entry_save(de); } diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 137c167d2..1a012c18d 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -54,6 +54,7 @@ typedef struct dvr_config { uint32_t dvr_extra_time_post; uint32_t dvr_update_window; int dvr_running; + uint32_t dvr_cleanup_threshold; muxer_config_t dvr_muxcnf; @@ -119,7 +120,24 @@ typedef enum { DVR_RS_EPG_WAIT, DVR_RS_FINISHED } dvr_rs_state_t; - + +typedef enum { + DVR_RET_DVRCONFIG = 0, + DVR_RET_1DAY = 1, + DVR_RET_3DAY = 3, + DVR_RET_5DAY = 5, + DVR_RET_1WEEK = 7, + DVR_RET_2WEEK = 14, + DVR_RET_3WEEK = 21, + DVR_RET_1MONTH = 30, + DVR_RET_2MONTH = 60, + DVR_RET_3MONTH = 90, + DVR_RET_6MONTH = 180, + DVR_RET_1YEAR = 365, + DVR_RET_ONREMOVE = UINT32_MAX-1, // for retention only + DVR_RET_SPACENEED = UINT32_MAX-1, // for removal only + DVR_RET_FOREVER = UINT32_MAX +} dvr_retention_t; typedef struct dvr_entry { @@ -430,6 +448,10 @@ static inline int dvr_entry_is_valid(dvr_entry_t *de) int dvr_entry_get_mc(dvr_entry_t *de); +const char *dvr_entry_get_retention_string ( dvr_entry_t *de ); + +const char *dvr_entry_get_removal_string ( dvr_entry_t *de ); + uint32_t dvr_entry_get_retention_days( dvr_entry_t *de ); uint32_t dvr_entry_get_removal_days( dvr_entry_t *de ); @@ -531,6 +553,8 @@ const char *dvr_get_filename(dvr_entry_t *de); int64_t dvr_get_filesize(dvr_entry_t *de); +int64_t dvr_entry_claenup(dvr_entry_t *de, int64_t requiredBytes); + void dvr_entry_set_rerecord(dvr_entry_t *de, int cmd); dvr_entry_t *dvr_entry_stop(dvr_entry_t *de); @@ -543,10 +567,14 @@ void dvr_entry_delete(dvr_entry_t *de, int no_missed_time_resched); void dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord); +void dvr_entry_destroy(dvr_entry_t *de, int delconf); + htsmsg_t *dvr_entry_class_mc_list (void *o, const char *lang); htsmsg_t *dvr_entry_class_pri_list(void *o, const char *lang); htsmsg_t *dvr_entry_class_config_name_list(void *o, const char *lang); htsmsg_t *dvr_entry_class_duration_list(void *o, const char *not_set, int max, int step, const char *lang); +htsmsg_t *dvr_entry_class_retention_list ( void *o, const char *lang ); +htsmsg_t *dvr_entry_class_removal_list ( void *o, const char *lang ); int dvr_entry_verify(dvr_entry_t *de, access_t *a, int readonly); diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index 3d2266914..7e9122727 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -1092,15 +1092,19 @@ const idclass_t dvr_autorec_entry_class = { { .type = PT_U32, .id = "retention", - .name = N_("DVR log retention (days)"), + .name = N_("DVR log retention"), + .def.i = DVR_RET_DVRCONFIG, .off = offsetof(dvr_autorec_entry_t, dae_retention), + .list = dvr_entry_class_retention_list, .opts = PO_HIDDEN | PO_EXPERT, }, { .type = PT_U32, .id = "removal", - .name = N_("DVR file retention period (days)"), + .name = N_("DVR file retention period"), + .def.i = DVR_RET_DVRCONFIG, .off = offsetof(dvr_autorec_entry_t, dae_removal), + .list = dvr_entry_class_removal_list, .opts = PO_HIDDEN | PO_EXPERT, }, { @@ -1389,8 +1393,14 @@ dvr_autorec_get_extra_time_post( dvr_autorec_entry_t *dae ) uint32_t dvr_autorec_get_retention_days( dvr_autorec_entry_t *dae ) { - if (dae->dae_retention > 0) + if (dae->dae_retention > 0) { + /* As we need the db entry when deleting the file on disk */ + if (dvr_autorec_get_removal_days(dae) != DVR_RET_FOREVER && + dvr_autorec_get_removal_days(dae) > dae->dae_retention) + return DVR_RET_ONREMOVE; + return dae->dae_retention; + } return dvr_retention_cleanup(dae->dae_config->dvr_retention_days); } diff --git a/src/dvr/dvr_config.c b/src/dvr/dvr_config.c index 988d3d1f4..4bdd14284 100644 --- a/src/dvr/dvr_config.c +++ b/src/dvr/dvr_config.c @@ -177,7 +177,8 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf) cfg->dvr_enabled = 1; cfg->dvr_config_name = strdup(name); - cfg->dvr_retention_days = 31; + cfg->dvr_retention_days = DVR_RET_ONREMOVE; + cfg->dvr_removal_days = DVR_RET_FOREVER; cfg->dvr_clone = 1; cfg->dvr_tag_files = 1; cfg->dvr_skip_commercials = 1; @@ -185,6 +186,7 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf) cfg->dvr_warm_time = 30; cfg->dvr_update_window = 24 * 3600; cfg->dvr_pathname = strdup("$t$n.$x"); + cfg->dvr_cleanup_threshold = 2000; /* Muxer config */ cfg->dvr_muxcnf.m_cache = MC_CACHE_DONTKEEP; @@ -514,8 +516,11 @@ dvr_config_save(dvr_config_t *cfg) lock_assert(&global_lock); dvr_config_storage_check(cfg); - if (cfg->dvr_removal_days > cfg->dvr_retention_days) - cfg->dvr_removal_days = cfg->dvr_retention_days; + if (cfg->dvr_cleanup_threshold < 100) + cfg->dvr_cleanup_threshold = 100; // as checking is only periodically, lower is not save + if (cfg->dvr_removal_days != DVR_RET_FOREVER && + cfg->dvr_removal_days > cfg->dvr_retention_days) + cfg->dvr_retention_days = DVR_RET_ONREMOVE; idnode_save(&cfg->dvr_id, m); hts_settings_save(m, "dvr/config/%s", idnode_uuid_as_sstr(&cfg->dvr_id)); htsmsg_destroy(m); @@ -694,6 +699,48 @@ dvr_config_class_cache_list(void *o, const char *lang) return strtab2htsmsg(tab, 1, lang); } +static htsmsg_t * +dvr_config_class_removal_list ( void *o, const char *lang ) +{ + static const struct strtab_u32 tab[] = { + { N_("1 day"), DVR_RET_1DAY }, + { N_("3 days"), DVR_RET_3DAY }, + { N_("5 days"), DVR_RET_5DAY }, + { N_("1 week"), DVR_RET_1WEEK }, + { N_("2 weeks"), DVR_RET_2WEEK }, + { N_("3 weeks"), DVR_RET_3WEEK }, + { N_("1 month"), DVR_RET_1MONTH }, + { N_("2 months"), DVR_RET_2MONTH }, + { N_("3 months"), DVR_RET_3MONTH }, + { N_("6 months"), DVR_RET_6MONTH }, + { N_("1 year"), DVR_RET_1YEAR }, + { N_("Until space needed"), DVR_RET_SPACENEED }, + { N_("Forever"), DVR_RET_FOREVER }, + }; + return strtab2htsmsg_u32(tab, 1, lang); +} + +static htsmsg_t * +dvr_config_class_retention_list ( void *o, const char *lang ) +{ + static const struct strtab_u32 tab[] = { + { N_("1 day"), DVR_RET_1DAY }, + { N_("3 days"), DVR_RET_3DAY }, + { N_("5 days"), DVR_RET_5DAY }, + { N_("1 week"), DVR_RET_1WEEK }, + { N_("2 weeks"), DVR_RET_2WEEK }, + { N_("3 weeks"), DVR_RET_3WEEK }, + { N_("1 month"), DVR_RET_1MONTH }, + { N_("2 months"), DVR_RET_2MONTH }, + { N_("3 months"), DVR_RET_3MONTH }, + { N_("6 months"), DVR_RET_6MONTH }, + { N_("1 year"), DVR_RET_1YEAR }, + { N_("On file removal"), DVR_RET_ONREMOVE }, + { N_("Forever"), DVR_RET_FOREVER }, + }; + return strtab2htsmsg_u32(tab, 1, lang); +} + static htsmsg_t * dvr_config_class_extra_list(void *o, const char *lang) { @@ -813,16 +860,19 @@ const idclass_t dvr_config_class = { { .type = PT_U32, .id = "retention-days", - .name = N_("DVR log retention period (days)"), + .name = N_("DVR log retention period"), .off = offsetof(dvr_config_t, dvr_retention_days), - .def.u32 = 31, + .def.u32 = DVR_RET_ONREMOVE, + .list = dvr_config_class_retention_list, .group = 1, }, { .type = PT_U32, .id = "removal-days", - .name = N_("DVR file retention period (days)"), + .name = N_("DVR file retention period"), .off = offsetof(dvr_config_t, dvr_removal_days), + .def.u32 = DVR_RET_FOREVER, + .list = dvr_config_class_removal_list, .group = 1, }, { @@ -927,6 +977,14 @@ const idclass_t dvr_config_class = { .off = offsetof(dvr_config_t, dvr_storage), .group = 2, }, + { + .type = PT_U32, + .id = "storage-cleanup", + .name = N_("\"Until space needed\" cleanup below (MB)"), + .off = offsetof(dvr_config_t, dvr_cleanup_threshold), + .def.i = 2000, //2000 MB + .group = 2, + }, { .type = PT_PERM, .id = "file-permissions", diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 9540ee37e..79c9ab892 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -42,7 +42,6 @@ static int dvr_in_init; static gtimer_t dvr_dbus_timer; #endif -static void dvr_entry_destroy(dvr_entry_t *de, int delconf); static void dvr_entry_set_timer(dvr_entry_t *de); static void dvr_timer_rerecord(void *aux); static void dvr_timer_expire(void *aux); @@ -256,11 +255,49 @@ dvr_entry_get_extra_time_post( dvr_entry_t *de ) return extra; } +const char * +dvr_entry_get_retention_string ( dvr_entry_t *de ) +{ + char buf[24]; + uint32_t retention = dvr_entry_get_retention_days(de); + + if (retention < DVR_RET_ONREMOVE) + snprintf(buf, sizeof(buf), "%i days", retention); + else if (retention == DVR_RET_ONREMOVE) + return strdup("On file removal"); + else + return strdup("Forever"); + + return strdup(buf); +} + +const char * +dvr_entry_get_removal_string ( dvr_entry_t *de ) +{ + char buf[24]; + uint32_t removal = dvr_entry_get_removal_days(de); + + if (removal < DVR_RET_SPACENEED) + snprintf(buf, sizeof(buf), "%i days", removal); + else if (removal == DVR_RET_SPACENEED) + return strdup("Until space needed"); + else + return strdup("Forever"); + + return strdup(buf); +} + uint32_t dvr_entry_get_retention_days( dvr_entry_t *de ) { - if (de->de_retention > 0) + if (de->de_retention > 0) { + /* As we need the db entry when deleting the file on disk */ + if (dvr_entry_get_removal_days(de) != DVR_RET_FOREVER && + dvr_entry_get_removal_days(de) > de->de_retention) + return DVR_RET_ONREMOVE; + return de->de_retention; + } return dvr_retention_cleanup(de->de_config->dvr_retention_days); } @@ -349,19 +386,25 @@ dvr_entry_retention_timer(dvr_entry_t *de) time_t stop = de->de_stop; uint32_t removal = dvr_entry_get_removal_days(de); uint32_t retention = dvr_entry_get_retention_days(de); - if (removal > retention) - removal = retention; + stop = de->de_stop + removal * (time_t)86400; - if (removal > 0 || retention == 0) { + if ((removal > 0 || retention == 0) && removal < DVR_RET_SPACENEED) { if (stop > dispatch_clock) { dvr_entry_retention_arm(de, dvr_timer_remove_files, stop); return; } if (dvr_get_filename(de)) - dvr_entry_delete(de, 1); + dvr_entry_delete(de, 1); // delete actual file + if (retention == DVR_RET_ONREMOVE) { + dvr_entry_destroy(de, 1); // also remove database entry + return; + } + } + + if (retention < DVR_RET_ONREMOVE) { + stop = de->de_stop + retention * (time_t)86400; + dvr_entry_retention_arm(de, dvr_timer_expire, stop); } - stop = de->de_stop + retention * (time_t)86400; - dvr_entry_retention_arm(de, dvr_timer_expire, stop); } /* @@ -935,7 +978,8 @@ delete_me: return 1; } not_so_good: - de->de_retention = 1; + de->de_retention = DVR_RET_ONREMOVE; + de->de_removal = DVR_RET_1DAY; dvr_entry_change_parent_child(de->de_parent, NULL, NULL, 1); dvr_entry_completed(de, SM_CODE_WEAK_STREAM); return 0; @@ -1173,7 +1217,7 @@ dvr_entry_dec_ref(dvr_entry_t *de) /** * */ -static void +void dvr_entry_destroy(dvr_entry_t *de, int delconf) { if (delconf) @@ -1772,7 +1816,7 @@ dvr_entry_start_recording(dvr_entry_t *de, int clone) return; } - gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de, + gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de, dvr_entry_get_stop_time(de)); } @@ -2137,18 +2181,48 @@ dvr_entry_class_pri_list ( void *o, const char *lang ) return strtab2htsmsg(tab, 1, lang); } -static int -dvr_entry_class_retention_set(void *o, const void *v) -{ - dvr_entry_t *de = (dvr_entry_t *)o; - return dvr_entry_class_int_set(de, (int *)&de->de_retention, *(int *)v); +htsmsg_t * +dvr_entry_class_retention_list ( void *o, const char *lang ) +{ + static const struct strtab_u32 tab[] = { + { N_("DVR configuration"), DVR_RET_DVRCONFIG }, + { N_("1 day"), DVR_RET_1DAY }, + { N_("3 days"), DVR_RET_3DAY }, + { N_("5 days"), DVR_RET_5DAY }, + { N_("1 week"), DVR_RET_1WEEK }, + { N_("2 weeks"), DVR_RET_2WEEK }, + { N_("3 weeks"), DVR_RET_3WEEK }, + { N_("1 month"), DVR_RET_1MONTH }, + { N_("2 months"), DVR_RET_2MONTH }, + { N_("3 months"), DVR_RET_3MONTH }, + { N_("6 months"), DVR_RET_6MONTH }, + { N_("1 year"), DVR_RET_1YEAR }, + { N_("On file removal"), DVR_RET_ONREMOVE }, + { N_("Forever"), DVR_RET_FOREVER }, + }; + return strtab2htsmsg_u32(tab, 1, lang); } -static int -dvr_entry_class_removal_set(void *o, const void *v) -{ - dvr_entry_t *de = (dvr_entry_t *)o; - return dvr_entry_class_int_set(de, (int *)&de->de_removal, *(int *)v); +htsmsg_t * +dvr_entry_class_removal_list ( void *o, const char *lang ) +{ + static const struct strtab_u32 tab[] = { + { N_("DVR configuration"), DVR_RET_DVRCONFIG }, + { N_("1 day"), DVR_RET_1DAY }, + { N_("3 days"), DVR_RET_3DAY }, + { N_("5 days"), DVR_RET_5DAY }, + { N_("1 week"), DVR_RET_1WEEK }, + { N_("2 weeks"), DVR_RET_2WEEK }, + { N_("3 weeks"), DVR_RET_3WEEK }, + { N_("1 month"), DVR_RET_1MONTH }, + { N_("2 months"), DVR_RET_2MONTH }, + { N_("3 months"), DVR_RET_3MONTH }, + { N_("6 months"), DVR_RET_6MONTH }, + { N_("1 year"), DVR_RET_1YEAR }, + { N_("Until space needed"), DVR_RET_SPACENEED }, + { N_("Forever"), DVR_RET_FOREVER }, + }; + return strtab2htsmsg_u32(tab, 1, lang); } static int @@ -2711,17 +2785,19 @@ const idclass_t dvr_entry_class = { { .type = PT_U32, .id = "retention", - .name = N_("DVR log retention (days)"), + .name = N_("DVR log retention"), .off = offsetof(dvr_entry_t, de_retention), - .set = dvr_entry_class_retention_set, + .def.i = DVR_RET_DVRCONFIG, + .list = dvr_entry_class_retention_list, .opts = PO_HIDDEN | PO_ADVANCED }, { .type = PT_U32, .id = "removal", - .name = N_("DVR file retention period (days)"), + .name = N_("DVR file retention period"), .off = offsetof(dvr_entry_t, de_removal), - .set = dvr_entry_class_removal_set, + .def.i = DVR_RET_DVRCONFIG, + .list = dvr_entry_class_removal_list, .opts = PO_HIDDEN | PO_ADVANCED }, { @@ -3017,12 +3093,12 @@ dvr_entry_delete(dvr_entry_t *de, int no_missed_time_resched) *tbuf = 0; tvhlog(LOG_INFO, "dvr", "delete entry %s \"%s\" on \"%s\" start time %s, " - "scheduled for recording by \"%s\", retention %d days, removal %d days", + "scheduled for recording by \"%s\", retention \"%s\" removal \"%s\"", idnode_uuid_as_sstr(&de->de_id), lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), tbuf, de->de_creator ?: "", - dvr_entry_get_retention_days(de), - dvr_entry_get_removal_days(de)); + dvr_entry_get_retention_string(de), + dvr_entry_get_removal_string(de)); if(!htsmsg_is_empty(de->de_files)) { #if ENABLE_INOTIFY diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index ce0449c19..6e78eb2d0 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -1541,12 +1541,183 @@ dvr_thread_epilog(dvr_entry_t *de, const char *dvr_postproc) /** * */ +static int dvr_disk_space_config_idx; +static int dvr_disk_space_config_size; +static time_t dvr_disk_space_config_lastdelete; static int64_t dvr_bfree; static int64_t dvr_btotal; static pthread_mutex_t dvr_disk_space_mutex; static gtimer_t dvr_disk_space_timer; static tasklet_t dvr_disk_space_tasklet; +/** + * Cleanup old recordings for this config until the dvr_cleanup_threshold is reached + * Only "Keep until space needed" recordings are deleted, starting with the oldest one + */ +static int64_t +dvr_disk_space_cleanup(dvr_config_t *cfg) +{ + dvr_entry_t *de, *oldest; + time_t stoptime; + int64_t requiredBytes, availBytes; + int64_t clearedBytes = 0, fileSize; + unsigned long int filesystemId; + struct statvfs diskdata; + struct tm tm; + int loops = 0; + char tbuf[64]; + const char *configName; + + if (!cfg || !cfg->dvr_enabled || statvfs(cfg->dvr_storage, &diskdata) == -1) + return -1; + + filesystemId = diskdata.f_fsid; + availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; + requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; + configName = cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"; + + /* When deleting a file from the disk, the system needs some time to actually do this */ + /* If calling this function to fast after the previous call, statvfs might be wrong/not updated yet */ + /* So we are risking to delete more files than needed, so allow 10s for the system to handle previous deletes */ + if (dvr_disk_space_config_lastdelete + 10 > dispatch_clock) { + tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\" is not allowed now", configName); + return -1; + } + + if (diskdata.f_bsize * (int64_t)diskdata.f_blocks < requiredBytes) { + tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\" is smaller than the total disk size!", + configName, requiredBytes/(int64_t)1024/(int64_t)1024); + return -1; + } + + tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", + configName, requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + while (availBytes < requiredBytes) { + oldest = NULL; + stoptime = dispatch_clock; + + LIST_FOREACH(de, &dvrentries, de_global_link) { + if (de->de_sched_state != DVR_COMPLETED && + de->de_sched_state != DVR_MISSED_TIME) + continue; + + if (dvr_entry_get_stop_time(de) > stoptime) + continue; + + if (dvr_entry_get_removal_days(de) != DVR_RET_SPACENEED) // only remove the allowed ones + continue; + + if (dvr_get_filename(de) == NULL || dvr_get_filesize(de) <= 0) + continue; + + if(statvfs(dvr_get_filename(de), &diskdata) == -1) + continue; + + /* Checking for the same config is useless as it's storage path might be changed meanwhile */ + /* Check for the same file system instead */ + if (filesystemId == 0 || diskdata.f_fsid != filesystemId) + continue; + + oldest = de; // the oldest one until now + stoptime = dvr_entry_get_stop_time(de); + } + + if (oldest) { + fileSize = dvr_get_filesize(oldest); + availBytes += fileSize; + clearedBytes += fileSize; + + localtime_r(&stoptime, &tm); + if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0) + *tbuf = 0; + tvhlog(LOG_INFO, "dvr","Delete \"until space needed\" recording \"%s\" with stop time \"%s\" and file size \"%ld MB\"", + lang_str_get(oldest->de_title, NULL), tbuf, fileSize/(int64_t)1024/(int64_t)1024); + + dvr_disk_space_config_lastdelete = dispatch_clock; + dvr_entry_delete(oldest, 1); // delete actual file + if (dvr_entry_get_retention_days(oldest) == DVR_RET_ONREMOVE) + dvr_entry_destroy(oldest, 1); // also delete database entry + } + else { + tvhlog(LOG_WARNING, "dvr","%s \"until space needed\" recordings found for config \"%s\", you are running out of disk space very soon!", + loops > 0 ? "Not enough" : "No", configName); + goto finish; + } + + loops++; + if (loops >= 10) { + tvhlog(LOG_WARNING, "dvr","Not able to clear the required disk space after deleting %i \"until space needed\" recordings...", loops); + goto finish; + } + } + +finish: + tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", cleared \"%ld MB\" of disk space, new free disk space \"%ld MB\"", + configName, clearedBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + return clearedBytes; +} + +/** + * Check for each dvr config if the free disk size is below the dvr_cleanup_threshold + * If so and we are using the dvr config ATM (active recording), we start the cleanup procedure + */ +static void +dvr_disk_space_check() +{ + dvr_config_t *cfg; + dvr_entry_t *de; + struct statvfs diskdata; + int64_t requiredBytes, availBytes; + int idx = 0, cleanupDone = 0; + + dvr_disk_space_config_idx++; + if (dvr_disk_space_config_idx > dvr_disk_space_config_size) + dvr_disk_space_config_idx = 1; + + LIST_FOREACH(cfg, &dvrconfigs, config_link) + { + idx++; + + if (cfg->dvr_enabled && + !cleanupDone && + idx >= dvr_disk_space_config_idx && + statvfs(cfg->dvr_storage, &diskdata) != -1) + { + availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; + requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; + + if (availBytes < requiredBytes) { + LIST_FOREACH(de, &dvrentries, de_global_link) { + + /* only start cleanup if we are actually writing files right now */ + if (de->de_sched_state != DVR_RECORDING || !de->de_config || de->de_config != cfg) + continue; + + tvhlog(LOG_WARNING, "dvr","running out of free disk space for dvr config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", + cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile", + requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + /* only cleanup one directory at the time as the system needs time to delete the actual files */ + dvr_disk_space_cleanup(de->de_config); + cleanupDone = 1; + dvr_disk_space_config_idx = idx; + break; + } + } + else { + tvhlog(LOG_DEBUG, "dvr","checking free disk space for config \"%s\" : OK", + cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"); + } + } + } + + if (!cleanupDone) + dvr_disk_space_config_idx = 0; + + dvr_disk_space_config_size = idx; +} /** * */ @@ -1571,9 +1742,14 @@ dvr_get_disk_space_tcb(void *opaque, int dearmed) if (!dearmed) { htsmsg_t *m = htsmsg_create_map(); pthread_mutex_lock(&dvr_disk_space_mutex); + + /* update disk space from default dvr config */ dvr_get_disk_space_update((char *)opaque); htsmsg_add_s64(m, "freediskspace", dvr_bfree); htsmsg_add_s64(m, "totaldiskspace", dvr_btotal); + + /* check free disk space for each dvr config and start cleanup if needed */ + dvr_disk_space_check(); pthread_mutex_unlock(&dvr_disk_space_mutex); notify_by_msg("diskspaceUpdate", m, 0); @@ -1595,7 +1771,7 @@ dvr_get_disk_space_cb(void *aux) path = strdup(cfg->dvr_storage); tasklet_arm(&dvr_disk_space_tasklet, dvr_get_disk_space_tcb, path); } - gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 60); + gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 15); } /** @@ -1607,7 +1783,7 @@ dvr_disk_space_init(void) dvr_config_t *cfg = dvr_config_find_by_name_default(NULL); pthread_mutex_init(&dvr_disk_space_mutex, NULL); dvr_get_disk_space_update(cfg->dvr_storage); - gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 60); + gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 5); } /** diff --git a/src/dvr/dvr_timerec.c b/src/dvr/dvr_timerec.c index 14d923643..052b8a9bd 100644 --- a/src/dvr/dvr_timerec.c +++ b/src/dvr/dvr_timerec.c @@ -609,15 +609,19 @@ const idclass_t dvr_timerec_entry_class = { { .type = PT_U32, .id = "retention", - .name = N_("DVR log retention (days)"), + .name = N_("DVR log retention"), + .def.i = DVR_RET_DVRCONFIG, .off = offsetof(dvr_timerec_entry_t, dte_retention), + .list = dvr_entry_class_retention_list, .opts = PO_EXPERT }, { .type = PT_U32, .id = "removal", - .name = N_("DVR file retention period (days)"), + .name = N_("DVR file retention period"), + .def.i = DVR_RET_DVRCONFIG, .off = offsetof(dvr_timerec_entry_t, dte_removal), + .list = dvr_entry_class_removal_list, .opts = PO_EXPERT }, { @@ -760,8 +764,14 @@ timerec_destroy_by_config(dvr_config_t *kcfg, int delconf) uint32_t dvr_timerec_get_retention_days( dvr_timerec_entry_t *dte ) { - if (dte->dte_retention > 0) + if (dte->dte_retention > 0) { + /* As we need the db entry when deleting the file on disk */ + if (dvr_timerec_get_removal_days(dte) != DVR_RET_FOREVER && + dvr_timerec_get_removal_days(dte) > dte->dte_retention) + return DVR_RET_ONREMOVE; + return dte->dte_retention; + } return dvr_retention_cleanup(dte->dte_config->dvr_retention_days); } diff --git a/src/hts_strtab.h b/src/hts_strtab.h index ac71067df..936199506 100644 --- a/src/hts_strtab.h +++ b/src/hts_strtab.h @@ -29,6 +29,11 @@ struct strtab { int val; }; +struct strtab_u32 { + const char *str; + uint32_t val; +}; + static int str2val0(const char *str, const struct strtab tab[], int l) __attribute((unused)); @@ -96,4 +101,20 @@ strtab2htsmsg0(const struct strtab tab[], int n, int i18n, const char *lang) #define strtab2htsmsg(tab,i18n,lang) strtab2htsmsg0(tab, sizeof(tab) / sizeof(tab[0]), i18n, lang) +static inline htsmsg_t * +strtab2htsmsg0_u32(const struct strtab_u32 tab[], uint32_t n, int i18n, const char *lang) +{ + uint32_t i; + htsmsg_t *e, *l = htsmsg_create_list(); + for (i = 0; i < n; i++) { + e = htsmsg_create_map(); + htsmsg_add_u32(e, "key", tab[i].val); + htsmsg_add_str(e, "val", i18n ? tvh_gettext_lang(lang, tab[i].str) : tab[i].str); + htsmsg_add_msg(l, NULL, e); + } + return l; +} + +#define strtab2htsmsg_u32(tab,i18n,lang) strtab2htsmsg0_u32(tab, sizeof(tab) / sizeof(tab[0]), i18n, lang) + #endif /* STRTAB_H_ */ diff --git a/src/htsp_server.c b/src/htsp_server.c index b0ba34da3..d61f15d1d 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -596,9 +596,9 @@ serierec_convert(htsp_connection_t *htsp, htsmsg_t *in, channel_t *ch, int autor if (!(retval = htsmsg_get_u32(in, "enabled", &u32)) || add) htsmsg_add_u32(conf, "enabled", !retval ? (u32 > 0 ? 1 : 0) : 1); // default on if (!(retval = htsmsg_get_u32(in, "retention", &u32)) || add) - htsmsg_add_u32(conf, "retention", !retval ? u32 : 0); // 0 = dvr config + htsmsg_add_u32(conf, "retention", !retval ? u32 : DVR_RET_DVRCONFIG); if (!(retval = htsmsg_get_u32(in, "removal", &u32)) || add) - htsmsg_add_u32(conf, "removal", !retval ? u32 : 0); // 0 = dvr config + htsmsg_add_u32(conf, "removal", !retval ? u32 : DVR_RET_DVRCONFIG); if(!(retval = htsmsg_get_u32(in, "priority", &u32)) || add) htsmsg_add_u32(conf, "pri", !retval ? u32 : DVR_PRIO_NORMAL); if ((str = htsmsg_get_str(in, "name")) || add) @@ -843,7 +843,7 @@ htsp_build_tag(channel_tag_t *ct, const char *method, int include_channels) * */ static htsmsg_t * -htsp_build_dvrentry(dvr_entry_t *de, const char *method, const char *lang) +htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method, const char *lang) { htsmsg_t *out = htsmsg_create_map(), *l, *m, *e; htsmsg_field_t *f; @@ -869,7 +869,13 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method, const char *lang) htsmsg_add_s64(out, "stop", de->de_stop); htsmsg_add_s64(out, "startExtra", dvr_entry_get_extra_time_pre(de)); htsmsg_add_s64(out, "stopExtra", dvr_entry_get_extra_time_post(de)); - htsmsg_add_u32(out, "retention", dvr_entry_get_retention_days(de)); + + if (htsp->htsp_version > 25) + htsmsg_add_u32(out, "retention", dvr_entry_get_retention_days(de)); + else + htsmsg_add_u32(out, "retention", dvr_entry_get_retention_days(de) == DVR_RET_ONREMOVE ? + dvr_entry_get_removal_days(de) : dvr_entry_get_retention_days(de)); + htsmsg_add_u32(out, "removal", dvr_entry_get_removal_days(de)); htsmsg_add_u32(out, "priority", de->de_pri); htsmsg_add_u32(out, "contentType", de->de_content_type); @@ -959,7 +965,7 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method, const char *lang) * */ static htsmsg_t * -htsp_build_autorecentry(dvr_autorec_entry_t *dae, const char *method) +htsp_build_autorecentry(htsp_connection_t *htsp, dvr_autorec_entry_t *dae, const char *method) { htsmsg_t *out = htsmsg_create_map(); int tad; @@ -968,7 +974,13 @@ htsp_build_autorecentry(dvr_autorec_entry_t *dae, const char *method) htsmsg_add_u32(out, "enabled", dae->dae_enabled >= 1 ? 1 : 0); htsmsg_add_u32(out, "maxDuration", dae->dae_maxduration); htsmsg_add_u32(out, "minDuration", dae->dae_minduration); - htsmsg_add_u32(out, "retention", dvr_autorec_get_retention_days(dae)); + + if (htsp->htsp_version > 25) + htsmsg_add_u32(out, "retention", dvr_autorec_get_retention_days(dae)); + else + htsmsg_add_u32(out, "retention", dvr_autorec_get_retention_days(dae) == DVR_RET_ONREMOVE ? + dvr_autorec_get_removal_days(dae) : dvr_autorec_get_retention_days(dae)); + htsmsg_add_u32(out, "removal", dvr_autorec_get_removal_days(dae)); htsmsg_add_u32(out, "daysOfWeek", dae->dae_weekdays); if (dae->dae_start >= 0 && dae->dae_start_window >= 0) { @@ -1014,14 +1026,20 @@ htsp_build_autorecentry(dvr_autorec_entry_t *dae, const char *method) * */ static htsmsg_t * -htsp_build_timerecentry(dvr_timerec_entry_t *dte, const char *method) +htsp_build_timerecentry(htsp_connection_t *htsp, dvr_timerec_entry_t *dte, const char *method) { htsmsg_t *out = htsmsg_create_map(); htsmsg_add_str(out, "id", idnode_uuid_as_sstr(&dte->dte_id)); htsmsg_add_u32(out, "enabled", dte->dte_enabled >= 1 ? 1 : 0); htsmsg_add_u32(out, "daysOfWeek", dte->dte_weekdays); - htsmsg_add_u32(out, "retention", dvr_timerec_get_retention_days(dte)); + + if (htsp->htsp_version > 25) + htsmsg_add_u32(out, "retention", dvr_timerec_get_retention_days(dte)); + else + htsmsg_add_u32(out, "retention", dvr_timerec_get_retention_days(dte) == DVR_RET_ONREMOVE ? + dvr_timerec_get_removal_days(dte) : dvr_timerec_get_retention_days(dte)); + htsmsg_add_u32(out, "removal", dvr_timerec_get_removal_days(dte)); htsmsg_add_u32(out, "priority", dte->dte_pri); htsmsg_add_s32(out, "start", dte->dte_start); @@ -1379,18 +1397,18 @@ htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in) /* Send all autorecs */ TAILQ_FOREACH(dae, &autorec_entries, dae_link) if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1)) - htsp_send_message(htsp, htsp_build_autorecentry(dae, "autorecEntryAdd"), NULL); + htsp_send_message(htsp, htsp_build_autorecentry(htsp, dae, "autorecEntryAdd"), NULL); /* Send all timerecs */ TAILQ_FOREACH(dte, &timerec_entries, dte_link) if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1)) - htsp_send_message(htsp, htsp_build_timerecentry(dte, "timerecEntryAdd"), NULL); + htsp_send_message(htsp, htsp_build_timerecentry(htsp, dte, "timerecEntryAdd"), NULL); /* Send all DVR entries */ LIST_FOREACH(de, &dvrentries, de_global_link) if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1) && htsp_user_access_channel(htsp, de->de_channel)) - htsp_send_message(htsp, htsp_build_dvrentry(de, "dvrEntryAdd", htsp->htsp_language), NULL); + htsp_send_message(htsp, htsp_build_dvrentry(htsp, de, "dvrEntryAdd", htsp->htsp_language), NULL); /* Send EPG updates */ if (epg) { @@ -1705,9 +1723,9 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) if(htsmsg_get_u32(in, "priority", &priority)) priority = DVR_PRIO_NORMAL; if(htsmsg_get_u32(in, "retention", &retention)) - retention = 0; + retention = DVR_RET_DVRCONFIG; if(htsmsg_get_u32(in, "removal", &removal)) - removal = 0; + removal = DVR_RET_DVRCONFIG; comment = htsmsg_get_str(in, "comment"); if (!(lang = htsmsg_get_str(in, "language"))) lang = htsp->htsp_language; @@ -1814,8 +1832,8 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) stop = htsmsg_get_s64_or_default(in, "stop", 0); start_extra = htsmsg_get_s64_or_default(in, "startExtra", 0); stop_extra = htsmsg_get_s64_or_default(in, "stopExtra", 0); - retention = htsmsg_get_u32_or_default(in, "retention", 0); - removal = htsmsg_get_u32_or_default(in, "removal", 0); + retention = htsmsg_get_u32_or_default(in, "retention", DVR_RET_DVRCONFIG); + removal = htsmsg_get_u32_or_default(in, "removal", DVR_RET_DVRCONFIG); priority = htsmsg_get_u32_or_default(in, "priority", DVR_PRIO_NORMAL); title = htsmsg_get_str(in, "title"); subtitle = htsmsg_get_str(in, "subtitle"); @@ -3390,7 +3408,7 @@ _htsp_dvr_entry_update(dvr_entry_t *de, const char *method, htsmsg_t *msg) if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1) && htsp_user_access_channel(htsp, de->de_channel)) { htsmsg_t *m = msg ? htsmsg_copy(msg) - : htsp_build_dvrentry(de, method, htsp->htsp_language); + : htsp_build_dvrentry(htsp, de, method, htsp->htsp_language); htsp_send_message(htsp, m, NULL); } } @@ -3441,7 +3459,7 @@ _htsp_autorec_entry_update(dvr_autorec_entry_t *dae, const char *method, htsmsg_ if ((dae->dae_channel == NULL || htsp_user_access_channel(htsp, dae->dae_channel)) && !dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1)) { htsmsg_t *m = msg ? htsmsg_copy(msg) - : htsp_build_autorecentry(dae, method); + : htsp_build_autorecentry(htsp, dae, method); htsp_send_message(htsp, m, NULL); } } @@ -3494,7 +3512,7 @@ _htsp_timerec_entry_update(dvr_timerec_entry_t *dte, const char *method, htsmsg_ if ((dte->dte_channel == NULL || htsp_user_access_channel(htsp, dte->dte_channel)) && !dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1)) { htsmsg_t *m = msg ? htsmsg_copy(msg) - : htsp_build_timerecentry(dte, method); + : htsp_build_timerecentry(htsp, dte, method); htsp_send_message(htsp, m, NULL); } } diff --git a/src/webui/simpleui.c b/src/webui/simpleui.c index 2134dd81a..d6888a005 100644 --- a/src/webui/simpleui.c +++ b/src/webui/simpleui.c @@ -336,7 +336,8 @@ page_einfo(http_connection_t *hc, const char *remain, void *opaque) if((http_arg_get(&hc->hc_req_args, "rec")) != NULL) { de = dvr_entry_create_by_event(1, NULL, e, 0, 0, hc->hc_username ?: NULL, hc->hc_representative ?: NULL, NULL, - DVR_PRIO_NORMAL, 0, 0, "simpleui"); + DVR_PRIO_NORMAL, DVR_RET_DVRCONFIG, + DVR_RET_DVRCONFIG, "simpleui"); } else if(de != NULL && (http_arg_get(&hc->hc_req_args, "cancel")) != NULL) { de = dvr_entry_cancel(de, 0); }