#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;
} 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,
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,
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
*/
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);
/**
*
#if ENABLE_INOTIFY
dvr_inotify_init();
#endif
+ dvr_disk_space_boot();
dvr_autorec_init();
dvr_timerec_init();
dvr_entry_init();
if (!clone)
dvr_entry_set_timer(de);
htsp_dvr_entry_add(de);
+ dvr_vfs_refresh_entry(de);
return de;
}
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);
}
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;
}
}
+ dvr_vfs_refresh_entry(de);
htsp_dvr_entry_update(de);
idnode_notify_changed(&de->de_id);
}
de->de_chain = NULL;
profile_chain_close(prch);
free(prch);
+
+ dvr_vfs_refresh_entry(de);
}
/**
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;
}
#include "dvr.h"
#include "atomic.h"
#include "notify.h"
-
-#if ENABLE_ANDROID
-#include <sys/vfs.h>
-#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 <sys/statvfs.h>
-#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
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";
struct statvfs diskdata;
int64_t requiredBytes, maximalBytes, availBytes, usedBytes;
int idx = 0, cleanupDone = 0;
+ dvr_vfs_t *dvfs;
pthread_mutex_lock(&global_lock);
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);
*
*/
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);
}
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);
}
gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 15);
}
+/**
+ *
+ */
+void
+dvr_disk_space_boot(void)
+{
+ LIST_INIT(&dvrvfs_list);
+}
+
/**
*
*/
{
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);
}
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;
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;
}
*/
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);
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TVH_VFS_H__
+#define __TVH_VFS_H__
+
+#if ENABLE_ANDROID
+#include <sys/vfs.h>
+#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 <sys/statvfs.h>
+#define tvh_fsid_t unsigned long
+#define tvh_fsid(x) (x)
+#endif
+
+#endif /* __TVH_VFS_H__ */
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;
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);
}
};
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);
}
/**
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 =
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)
val = parseInt(val / 1024) + _('KiB');
return val;
};
- text = _('Storage space') + ': <b>' + human(bfree) + '/' + human(btotal) + '</b>';
+ text = _('Storage space') + ': <b>' + human(bfree) + '/' + human(bused) + '/' + human(btotal) + '</b>';
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) {