From: Jaroslav Kysela Date: Fri, 11 Dec 2015 13:59:25 +0000 (+0100) Subject: DVR: handle the used space correctly X-Git-Tag: v4.2.1~1318 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ba7f66dba2b86412a9d7f025e8832ac7d8bbd3c2;p=thirdparty%2Ftvheadend.git DVR: handle the used space correctly --- diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index a33bd80e0..cf99e944f 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -26,6 +26,13 @@ #include "muxer.h" #include "profile.h" #include "lang_str.h" +#include "tvhvfs.h" + +typedef struct dvr_vfs { + LIST_ENTRY(dvr_vfs) link; + tvh_fsid_t fsid; + uint64_t used_size; +} dvr_vfs_t; typedef struct dvr_config { idnode_t dvr_id; @@ -85,10 +92,6 @@ typedef struct dvr_config { } dvr_config_t; -extern struct dvr_config_list dvrconfigs; - -extern struct dvr_entry_list dvrentries; - typedef enum { DVR_PRIO_IMPORTANT = 0, DVR_PRIO_HIGH = 1, @@ -98,10 +101,6 @@ typedef enum { DVR_PRIO_NOTSET = 5, } dvr_prio_t; - -LIST_HEAD(dvr_rec_stream_list, dvr_rec_stream); - - typedef enum { DVR_SCHEDULED, /* Scheduled for recording (in the future) */ DVR_RECORDING, @@ -405,6 +404,10 @@ extern const idclass_t dvr_entry_class; extern const idclass_t dvr_autorec_entry_class; extern const idclass_t dvr_timerec_entry_class; +extern struct dvr_vfs_list dvrvfs_list; +extern struct dvr_config_list dvrconfigs; +extern struct dvr_entry_list dvrentries; + /** * Prototypes */ @@ -583,9 +586,13 @@ int dvr_entry_verify(dvr_entry_t *de, access_t *a, int readonly); void dvr_spawn_postcmd(dvr_entry_t *de, const char *postcmd, const char *filename); +void dvr_vfs_refresh_entry(dvr_entry_t *de); +void dvr_vfs_remove_entry(dvr_entry_t *de); + +void dvr_disk_space_boot(void); void dvr_disk_space_init(void); void dvr_disk_space_done(void); -int dvr_get_disk_space(int64_t *bfree, int64_t *btotal); +int dvr_get_disk_space(int64_t *bfree, int64_t *bused, int64_t *btotal); /** * diff --git a/src/dvr/dvr_config.c b/src/dvr/dvr_config.c index 125d383f0..7e42bb805 100644 --- a/src/dvr/dvr_config.c +++ b/src/dvr/dvr_config.c @@ -1218,6 +1218,7 @@ dvr_init(void) #if ENABLE_INOTIFY dvr_inotify_init(); #endif + dvr_disk_space_boot(); dvr_autorec_init(); dvr_timerec_init(); dvr_entry_init(); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 6b87b4f28..e8f2d33c6 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -755,6 +755,7 @@ dvr_entry_create(const char *uuid, htsmsg_t *conf, int clone) if (!clone) dvr_entry_set_timer(de); htsp_dvr_entry_add(de); + dvr_vfs_refresh_entry(de); return de; } @@ -1291,14 +1292,27 @@ dvr_entry_destroy_by_config(dvr_config_t *cfg, int delconf) void dvr_entry_save(dvr_entry_t *de) { - htsmsg_t *m = htsmsg_create_map(); + htsmsg_t *m = htsmsg_create_map(), *e, *l, *c; + htsmsg_field_t *f; char ubuf[UUID_HEX_SIZE]; + const char *filename; lock_assert(&global_lock); idnode_save(&de->de_id, m); - if (de->de_files) - htsmsg_add_msg(m, "files", htsmsg_copy(de->de_files)); + if (de->de_files) { + l = htsmsg_create_list(); + HTSMSG_FOREACH(f, de->de_files) + if ((e = htsmsg_field_get_map(f)) != NULL) { + filename = htsmsg_get_str(e, "filename"); + if (filename) { + c = htsmsg_create_map(); + htsmsg_add_str(c, "filename", filename); + htsmsg_add_msg(l, NULL, c); + } + } + htsmsg_add_msg(m, "files", l); + } hts_settings_save(m, "dvr/log/%s", idnode_uuid_as_str(&de->de_id, ubuf)); htsmsg_destroy(m); } @@ -3140,6 +3154,7 @@ dvr_entry_delete(dvr_entry_t *de, int no_missed_time_resched) if(cfg->dvr_title_dir || cfg->dvr_channel_dir || cfg->dvr_dir_per_day || de->de_directory) rdir = cfg->dvr_storage; + dvr_vfs_remove_entry(de); HTSMSG_FOREACH(f, de->de_files) { m = htsmsg_field_get_map(f); if (m == NULL) continue; diff --git a/src/dvr/dvr_inotify.c b/src/dvr/dvr_inotify.c index d134b4c95..87ed4b0a4 100644 --- a/src/dvr/dvr_inotify.c +++ b/src/dvr/dvr_inotify.c @@ -231,6 +231,7 @@ _dvr_inotify_moved } } + dvr_vfs_refresh_entry(de); htsp_dvr_entry_update(de); idnode_notify_changed(&de->de_id); } diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index f8e6aaea5..48047bdd0 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -168,6 +168,8 @@ dvr_rec_unsubscribe(dvr_entry_t *de) de->de_chain = NULL; profile_chain_close(prch); free(prch); + + dvr_vfs_refresh_entry(de); } /** @@ -804,6 +806,7 @@ cut1: m = htsmsg_create_map(); htsmsg_add_str(m, "filename", path); htsmsg_add_msg(de->de_files, NULL, m); + dvr_vfs_refresh_entry(de); return 0; } diff --git a/src/dvr/dvr_vfsmgr.c b/src/dvr/dvr_vfsmgr.c index 1faa217f3..882ce462f 100644 --- a/src/dvr/dvr_vfsmgr.c +++ b/src/dvr/dvr_vfsmgr.c @@ -29,31 +29,108 @@ #include "dvr.h" #include "atomic.h" #include "notify.h" - -#if ENABLE_ANDROID -#include -#define statvfs statfs -#define fstatvfs fstatfs -#define tvh_fsid_t uint64_t -#define tvh_fsid(x) (((uint64_t)x.__val[0] << 32) | (x.__val[1])) -#else -#include -#define tvh_fsid_t unsigned long -#define tvh_fsid(x) (x) -#endif +#include "tvhvfs.h" #define MIB(v) ((int64_t)v*((int64_t)1024*1024)) #define TOMIB(v) (v/((int64_t)1024*1024)) +struct dvr_vfs_list dvrvfs_list; + 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 int64_t dvr_bused; static pthread_mutex_t dvr_disk_space_mutex; static gtimer_t dvr_disk_space_timer; static tasklet_t dvr_disk_space_tasklet; +/* + * + */ +static dvr_vfs_t * +dvr_vfs_find(dvr_vfs_t *old, tvh_fsid_t id) +{ + dvr_vfs_t *dv; + + if (id == 0) + return NULL; + if (old && old->fsid == id) + return old; + LIST_FOREACH(dv, &dvrvfs_list, link) + if (dv->fsid == id) + return dv; + dv = calloc(1, sizeof(*dv)); + dv->fsid = id; + LIST_INSERT_HEAD(&dvrvfs_list, dv, link); + return dv; +} + +/* + * + */ +void +dvr_vfs_refresh_entry(dvr_entry_t *de) +{ + htsmsg_field_t *f; + htsmsg_t *m; + struct statvfs vst; + struct stat st; + dvr_vfs_t *vfs = NULL; + uint64_t size; + const char *filename; + + lock_assert(&global_lock); + if (de->de_files == NULL) + return; + HTSMSG_FOREACH(f, de->de_files) + if ((m = htsmsg_field_get_map(f)) != NULL) { + filename = htsmsg_get_str(m, "filename"); + vfs = dvr_vfs_find(vfs, htsmsg_get_s64(m, "fsid", 0)); + if (vfs) { + size = htsmsg_get_s64(m, "size", 0); + vfs->used_size = size <= vfs->used_size ? vfs->used_size - size : 0; + } + if(statvfs(filename, &vst) < 0 || stat(filename, &st) < 0) { + tvhlog(LOG_ERR, "dvr", "unable to stat file '%s'", filename); + htsmsg_delete_field(m, "fsid"); + htsmsg_delete_field(m, "size"); + continue; + } + vfs = dvr_vfs_find(vfs, tvh_fsid(vst.f_fsid)); + if (vfs && st.st_size > 0) { + htsmsg_set_s64(m, "fsid", tvh_fsid(vst.f_fsid)); + htsmsg_set_s64(m, "size", st.st_size); + vfs->used_size += st.st_size; + } + } +} + +/* + * + */ +void +dvr_vfs_remove_entry(dvr_entry_t *de) +{ + htsmsg_field_t *f; + htsmsg_t *m; + dvr_vfs_t *vfs = NULL; + uint64_t size; + + lock_assert(&global_lock); + HTSMSG_FOREACH(f, de->de_files) + if ((m = htsmsg_field_get_map(f)) != NULL) { + vfs = dvr_vfs_find(vfs, htsmsg_get_s64(m, "fsid", 0)); + if (vfs) { + size = htsmsg_get_s64(m, "size", 0); + vfs->used_size = size <= vfs->used_size ? vfs->used_size - size : 0; + } + htsmsg_delete_field(m, "fsid"); + htsmsg_delete_field(m, "size"); + } +} + /** * 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 @@ -71,15 +148,18 @@ dvr_disk_space_cleanup(dvr_config_t *cfg) int loops = 0; char tbuf[64]; const char *configName; + dvr_vfs_t *dvfs; if (!cfg || !cfg->dvr_enabled || statvfs(cfg->dvr_storage, &diskdata) == -1) return -1; + dvfs = dvr_vfs_find(NULL, tvh_fsid(diskdata.f_fsid)); + filesystemId = tvh_fsid(diskdata.f_fsid); availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; requiredBytes = MIB(cfg->dvr_cleanup_threshold_free); diskBytes = diskdata.f_bsize * (int64_t)diskdata.f_blocks; - usedBytes = diskBytes - availBytes; + usedBytes = dvfs->used_size; maximalBytes = MIB(cfg->dvr_cleanup_threshold_used); configName = cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"; @@ -179,6 +259,7 @@ dvr_disk_space_check() struct statvfs diskdata; int64_t requiredBytes, maximalBytes, availBytes, usedBytes; int idx = 0, cleanupDone = 0; + dvr_vfs_t *dvfs; pthread_mutex_lock(&global_lock); @@ -194,8 +275,10 @@ dvr_disk_space_check() idx >= dvr_disk_space_config_idx && statvfs(cfg->dvr_storage, &diskdata) != -1) { + dvfs = dvr_vfs_find(NULL, tvh_fsid(diskdata.f_fsid)); + availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; - usedBytes = (diskdata.f_bsize * (int64_t)diskdata.f_blocks) - availBytes; + usedBytes = dvfs->used_size; requiredBytes = MIB(cfg->dvr_cleanup_threshold_free); maximalBytes = MIB(cfg->dvr_cleanup_threshold_used); @@ -244,16 +327,24 @@ checking: * */ static void -dvr_get_disk_space_update(const char *path) +dvr_get_disk_space_update(const char *path, int locked) { struct statvfs diskdata; + dvr_vfs_t *dvfs; if(statvfs(path, &diskdata) == -1) return; + if (!locked) + pthread_mutex_lock(&global_lock); + dvfs = dvr_vfs_find(NULL, tvh_fsid(diskdata.f_fsid)); + if (!locked) + pthread_mutex_unlock(&global_lock); + pthread_mutex_lock(&dvr_disk_space_mutex); dvr_bfree = diskdata.f_bsize * (int64_t)diskdata.f_bavail; dvr_btotal = diskdata.f_bsize * (int64_t)diskdata.f_blocks; + dvr_bused = dvfs ? dvfs->used_size : 0; pthread_mutex_unlock(&dvr_disk_space_mutex); } @@ -263,21 +354,23 @@ dvr_get_disk_space_update(const char *path) static void dvr_get_disk_space_tcb(void *opaque, int dearmed) { + pthread_mutex_unlock(&tasklet_lock); + if (!dearmed) { htsmsg_t *m = htsmsg_create_map(); /* update disk space from default dvr config */ - dvr_get_disk_space_update((char *)opaque); + dvr_get_disk_space_update((char *)opaque, 0); htsmsg_add_s64(m, "freediskspace", dvr_bfree); + htsmsg_add_s64(m, "useddiskspace", dvr_bused); htsmsg_add_s64(m, "totaldiskspace", dvr_btotal); notify_by_msg("diskspaceUpdate", m, 0); /* check free disk space for each dvr config and start cleanup if needed */ - pthread_mutex_unlock(&tasklet_lock); dvr_disk_space_check(); - pthread_mutex_lock(&tasklet_lock); } + pthread_mutex_lock(&tasklet_lock); free(opaque); } @@ -297,6 +390,15 @@ dvr_get_disk_space_cb(void *aux) gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 15); } +/** + * + */ +void +dvr_disk_space_boot(void) +{ + LIST_INIT(&dvrvfs_list); +} + /** * */ @@ -305,7 +407,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); + dvr_get_disk_space_update(cfg->dvr_storage, 1); gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 5); } @@ -315,21 +417,28 @@ dvr_disk_space_init(void) void dvr_disk_space_done(void) { + dvr_vfs_t *vfs; + tasklet_disarm(&dvr_disk_space_tasklet); gtimer_disarm(&dvr_disk_space_timer); + while ((vfs = LIST_FIRST(&dvrvfs_list)) != NULL) { + LIST_REMOVE(vfs, link); + free(vfs); + } } /** * */ int -dvr_get_disk_space(int64_t *bfree, int64_t *btotal) +dvr_get_disk_space(int64_t *bfree, int64_t *bused, int64_t *btotal) { int res = 0; pthread_mutex_lock(&dvr_disk_space_mutex); if (dvr_bfree || dvr_btotal) { *bfree = dvr_bfree; + *bused = dvr_bused; *btotal = dvr_btotal; } else { res = -EINVAL; diff --git a/src/htsp_server.c b/src/htsp_server.c index ce79c733d..f808a0277 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -1292,13 +1292,14 @@ static htsmsg_t * htsp_method_getDiskSpace(htsp_connection_t *htsp, htsmsg_t *in) { htsmsg_t *out; - int64_t bfree, btotal; + int64_t bfree, bused, btotal; - if (dvr_get_disk_space(&bfree, &btotal)) + if (dvr_get_disk_space(&bfree, &bused, &btotal)) return htsp_error("Unable to stat path"); out = htsmsg_create_map(); htsmsg_add_s64(out, "freediskspace", bfree); + htsmsg_add_s64(out, "useddiskspace", bused); htsmsg_add_s64(out, "totaldiskspace", btotal); return out; } diff --git a/src/tvheadend.h b/src/tvheadend.h index 80b7ca3e8..35ca3344d 100644 --- a/src/tvheadend.h +++ b/src/tvheadend.h @@ -210,6 +210,7 @@ void tasklet_disarm(tasklet_t *gti); */ LIST_HEAD(access_entry_list, access_entry); LIST_HEAD(th_subscription_list, th_subscription); +LIST_HEAD(dvr_vfs_list, dvr_vfs); LIST_HEAD(dvr_config_list, dvr_config); LIST_HEAD(dvr_entry_list, dvr_entry); TAILQ_HEAD(ref_update_queue, ref_update); diff --git a/src/tvhvfs.h b/src/tvhvfs.h new file mode 100644 index 000000000..7e8ff29d2 --- /dev/null +++ b/src/tvhvfs.h @@ -0,0 +1,34 @@ +/* + * File system management + * Copyright (C) 2015 Jaroslav Kysela + * + * 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 . + */ + +#ifndef __TVH_VFS_H__ +#define __TVH_VFS_H__ + +#if ENABLE_ANDROID +#include +#define statvfs statfs +#define fstatvfs fstatfs +#define tvh_fsid_t uint64_t +#define tvh_fsid(x) (((uint64_t)x.__val[0] << 32) | (x.__val[1])) +#else +#include +#define tvh_fsid_t unsigned long +#define tvh_fsid(x) (x) +#endif + +#endif /* __TVH_VFS_H__ */ diff --git a/src/webui/comet.c b/src/webui/comet.c index 903af03d7..6b9de24c3 100644 --- a/src/webui/comet.c +++ b/src/webui/comet.c @@ -148,7 +148,7 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) htsmsg_t *m = htsmsg_create_map(); const char *username = hc->hc_access ? (hc->hc_access->aa_username ?: "") : ""; - int64_t bfree, btotal; + int64_t bfree, bused, btotal; int dvr = !http_access_verify(hc, ACCESS_RECORDER); int admin = !http_access_verify(hc, ACCESS_ADMIN); const char *s; @@ -182,8 +182,9 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) if (config.info_area && config.info_area[0]) htsmsg_add_str(m, "info_area", config.info_area); - if (dvr && !dvr_get_disk_space(&bfree, &btotal)) { + if (dvr && !dvr_get_disk_space(&bfree, &bused, &btotal)) { htsmsg_add_s64(m, "freediskspace", bfree); + htsmsg_add_s64(m, "useddiskspace", bused); htsmsg_add_s64(m, "totaldiskspace", btotal); } diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 1ea97ef4f..992cc8f5e 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -422,8 +422,8 @@ tvheadend.VideoPlayer = function(url) { }; function diskspaceUpdate(o) { - if ('freediskspace' in o && 'totaldiskspace' in o) - tvheadend.rootTabPanel.setDiskSpace(o.freediskspace, o.totaldiskspace); + if ('freediskspace' in o && 'useddiskspace' in o && 'totaldiskspace' in o) + tvheadend.rootTabPanel.setDiskSpace(o.freediskspace, o.useddiskspace, o.totaldiskspace); } /** @@ -453,8 +453,8 @@ function accessUpdate(o) { tvheadend.rootTabPanel.setLogin(o.username); if ('address' in o) tvheadend.rootTabPanel.setAddress(o.address); - if ('freediskspace' in o && 'totaldiskspace' in o) - tvheadend.rootTabPanel.setDiskSpace(o.freediskspace, o.totaldiskspace); + if ('freediskspace' in o && 'useddiskspace' in o && 'totaldiskspace' in o) + tvheadend.rootTabPanel.setDiskSpace(o.freediskspace, o.useddiskspace, o.totaldiskspace); if ('cookie_expires' in o && o.cookie_expires > 0) tvheadend.cookieProvider.expires = @@ -736,7 +736,7 @@ tvheadend.RootTabPanel = Ext.extend(Ext.TabPanel, { Ext.get(this.extra.login.tabEl).child('span.x-tab-strip-extra-comp', true).qtip = addr; }, - setDiskSpace: function(bfree, btotal) { + setDiskSpace: function(bfree, bused, btotal) { if (!('storage' in this.extra)) return; human = function(val) { if (val > 1073741824) @@ -747,10 +747,10 @@ tvheadend.RootTabPanel = Ext.extend(Ext.TabPanel, { val = parseInt(val / 1024) + _('KiB'); return val; }; - text = _('Storage space') + ': ' + human(bfree) + '/' + human(btotal) + ''; + text = _('Storage space') + ': ' + human(bfree) + '/' + human(bused) + '/' + human(btotal) + ''; var el = Ext.get(this.extra.storage.tabEl).child('span.x-tab-strip-extra-comp', true); el.innerHTML = text; - el.qtip = _('Free') + ': ' + human(bfree) + ' ' + _('Total') + ': ' + human(btotal); + el.qtip = _('Free') + ': ' + human(bfree) + ' ' + _('Used by tvheadend') + ': ' + human(bused) + ' ' + _('Total') + ': ' + human(btotal); }, setTime: function(stime) {