]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
DVR: handle the used space correctly
authorJaroslav Kysela <perex@perex.cz>
Fri, 11 Dec 2015 13:59:25 +0000 (14:59 +0100)
committerJaroslav Kysela <perex@perex.cz>
Fri, 11 Dec 2015 14:36:10 +0000 (15:36 +0100)
src/dvr/dvr.h
src/dvr/dvr_config.c
src/dvr/dvr_db.c
src/dvr/dvr_inotify.c
src/dvr/dvr_rec.c
src/dvr/dvr_vfsmgr.c
src/htsp_server.c
src/tvheadend.h
src/tvhvfs.h [new file with mode: 0644]
src/webui/comet.c
src/webui/static/app/tvheadend.js

index a33bd80e024ec250341c799983dd23d5113ef510..cf99e944f0ce3653a35d3c802146132a1da40152 100644 (file)
 #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);
 
 /**
  *
index 125d383f03e2f824e5400ffb62167441402bbbbf..7e42bb805eac3e711485107a46f7fe32de89829b 100644 (file)
@@ -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();
index 6b87b4f28c23e98d3146c4bdae513e6e0889ddbc..e8f2d33c67093250785bd2b42383c4215ecf7b9b 100644 (file)
@@ -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;
index d134b4c95391245ef8d3e01ec7e52258cc0152a6..87ed4b0a4b4039ffadf0fb1db2cee52a0f83b92d 100644 (file)
@@ -231,6 +231,7 @@ _dvr_inotify_moved
     }
   }
   
+  dvr_vfs_refresh_entry(de);
   htsp_dvr_entry_update(de);
   idnode_notify_changed(&de->de_id);
 }
index f8e6aaea53a2537995ca3081868171674138faae..48047bdd08200e4f08a5679ed0089e42ab0d4d0d 100644 (file)
@@ -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;
 }
index 1faa217f366017b124a7ed77bf13698fcb237b1f..882ce462f60fba0cb1f04d84b1236bcde0c5936f 100644 (file)
 #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
@@ -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;
index ce79c733dd995ab1545c791f4b64b33c5891e958..f808a0277181b2fddfb334b1da28b821784b552f 100644 (file)
@@ -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;
 }
index 80b7ca3e87642d7ed3634ac13b21ceec096c7ccf..35ca3344d5a5de782b2ff93c833b09138ee63f4e 100644 (file)
@@ -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 (file)
index 0000000..7e8ff29
--- /dev/null
@@ -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 <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__ */
index 903af03d7200129447af90e51cd92cad729f80f1..6b9de24c38c1c953a9beb3d8ca2985f566641451 100644 (file)
@@ -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);
   }
 
index 1ea97ef4fae440925f7775d81b95ef2413e9c98d..992cc8f5efe18065fcc6f35e6ea5156edea2bb95 100644 (file)
@@ -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') + ':&nbsp;<b>' + human(bfree) + '/' + human(btotal) + '</b>';
+        text = _('Storage space') + ':&nbsp;<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) {