]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
idnode, imagecache: introduce simple node system
authorJaroslav Kysela <perex@perex.cz>
Thu, 10 Sep 2015 12:12:04 +0000 (14:12 +0200)
committerJaroslav Kysela <perex@perex.cz>
Thu, 10 Sep 2015 12:13:55 +0000 (14:13 +0200)
src/api.h
src/api/api_idnode.c
src/api/api_imagecache.c
src/idnode.c
src/idnode.h
src/imagecache.c
src/imagecache.h
src/webui/static/app/config.js
src/webui/static/app/idnode.js

index 81a33ab34d65aa4d81a5d136000d21ef2dd4d890..c1d30da2822ac5172b6a6ac7d85335e1810f8344 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -25,7 +25,7 @@
 #include "redblack.h"
 #include "access.h"
 
-#define TVH_API_VERSION 15
+#define TVH_API_VERSION 16
 
 /*
  * Command hook
@@ -111,6 +111,12 @@ int api_idnode_handler
   ( access_t *perm, htsmsg_t *args, htsmsg_t **resp,
     void (*handler)(access_t *perm, idnode_t *in), const char *op );
 
+int api_idnode_load_simple
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
+
+int api_idnode_save_simple
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
+
 /*
  * Service mapper
  */
index bfc3d16f93c3bf839482ffb36c652bb76ec4305e..43d8c31b3d2df25cc4d6782f0265f7080f75376e 100644 (file)
@@ -237,8 +237,7 @@ api_idnode_load
 {
   int err = 0, meta, count = 0;
   idnode_t *in;
-  htsmsg_t *uuids, *l = NULL, *m;
-  htsmsg_t *flist;
+  htsmsg_t *uuids, *l = NULL, *m, *flist;
   htsmsg_field_t *f;
   const char *uuid = NULL, *class;
 
@@ -320,6 +319,52 @@ api_idnode_load
   return err;
 }
 
+int
+api_idnode_load_simple
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  int err = 0, meta;
+  htsmsg_t *l = NULL, *m, *flist;
+  const char *class;
+  idnode_t *in = (idnode_t *)opaque;
+
+  /* Class based */
+  if ((class = htsmsg_get_str(args, "class"))) {
+    pthread_mutex_lock(&global_lock);
+    err = api_idnode_load_by_class0(perm, (void*)in->in_class, NULL, args, resp);
+    pthread_mutex_unlock(&global_lock);
+    return err;
+  }
+
+  /* UUIDs */
+  meta = htsmsg_get_s32_or_default(args, "meta", 0);
+
+  flist = api_idnode_flist_conf(args, "list");
+
+  pthread_mutex_lock(&global_lock);
+
+  if (!idnode_perm(in, perm, NULL)) {
+    l = htsmsg_create_list();
+    m = idnode_serialize0(in, flist, 0, perm->aa_lang);
+    if (meta > 0)
+      htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0, perm->aa_lang));
+    htsmsg_add_msg(l, NULL, m);
+  } else {
+    err = EPERM;
+  }
+
+  pthread_mutex_unlock(&global_lock);
+
+  if (l) {
+    *resp = htsmsg_create_map();
+    htsmsg_add_msg(*resp, "entries", l);
+  }
+
+  htsmsg_destroy(flist);
+
+  return err;
+}
+
 static int
 api_idnode_save
   ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
@@ -384,6 +429,35 @@ exit:
   return err;
 }
 
+int
+api_idnode_save_simple
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  int err = 0;
+  htsmsg_t *msg;
+  htsmsg_field_t *f;
+  idnode_t *in = (idnode_t *)opaque;
+
+  if (!(f = htsmsg_field_find(args, "node")))
+    return EINVAL;
+  if (!(msg = htsmsg_field_get_map(f)))
+      return EINVAL;
+
+  pthread_mutex_lock(&global_lock);
+
+  /* Single */
+  if (!idnode_perm(in, perm, msg)) {
+    idnode_update(in, msg);
+    idnode_perm_unset(in);
+  } else {
+    err = EPERM;
+  }
+
+  pthread_mutex_unlock(&global_lock);
+
+  return err;
+}
+
 int
 api_idnode_tree
   ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
index d8accb6baacd41f5f78d7ea0c709451190d29f1f..9ddd2f75950c18497930dd5cde057b8950a2f005 100644 (file)
 #if ENABLE_IMAGECACHE
 
 static int
-api_imagecache_load
-  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
-{
-  htsmsg_t *l;
-  pthread_mutex_lock(&global_lock);
-  *resp = htsmsg_create_map();
-  l     = htsmsg_create_list();
-  htsmsg_add_msg(l, NULL, imagecache_get_config());
-  htsmsg_add_msg(*resp, "entries", l);
-  pthread_mutex_unlock(&global_lock);
-  return 0;
-}
-
-static int
-api_imagecache_save
+api_imagecache_clean
   ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
 {
   int b;
-
-  pthread_mutex_lock(&global_lock);
-  if (imagecache_set_config(args))
-    imagecache_save();
-  if (!htsmsg_get_bool(args, "clean", &b) && b)
+  if (htsmsg_get_bool(args, "clean", &b))
+    return EINVAL;
+  if (b) {
+    pthread_mutex_lock(&global_lock);
     imagecache_clean();
-  pthread_mutex_unlock(&global_lock);
-  *resp = htsmsg_create_map();
-  htsmsg_add_u32(*resp, "success", 1);
+    pthread_mutex_unlock(&global_lock);
+  }
   return 0;
 }
 
@@ -60,8 +44,9 @@ void
 api_imagecache_init ( void )
 {
   static api_hook_t ah[] = {
-    { "imagecache/config/load", ACCESS_ADMIN, api_imagecache_load, NULL },
-    { "imagecache/config/save", ACCESS_ADMIN, api_imagecache_save, NULL },
+    { "imagecache/config/load",  ACCESS_ADMIN, api_idnode_load_simple, &imagecache_conf },
+    { "imagecache/config/save",  ACCESS_ADMIN, api_idnode_save_simple, &imagecache_conf },
+    { "imagecache/config/clean", ACCESS_ADMIN, api_imagecache_clean, NULL },
     { NULL },
   };
 
index b08348e830c10464522db42c42a96ba0f4c8407b..e9b160f1733fc9f1ac861a250bf673cccda14cd2 100644 (file)
@@ -1314,9 +1314,11 @@ idnode_serialize0(idnode_t *self, htsmsg_t *list, int optmask, const char *lang)
   const char *uuid, *s;
 
   htsmsg_t *m = htsmsg_create_map();
-  uuid = idnode_uuid_as_sstr(self);
-  htsmsg_add_str(m, "uuid", uuid);
-  htsmsg_add_str(m, "id",   uuid);
+  if (!idc->ic_snode) {
+    uuid = idnode_uuid_as_sstr(self);
+    htsmsg_add_str(m, "uuid", uuid);
+    htsmsg_add_str(m, "id",   uuid);
+  }
   htsmsg_add_str(m, "text", idnode_get_title(self, lang) ?: "");
   if ((s = idclass_get_caption(idc, lang)))
     htsmsg_add_str(m, "caption", s);
@@ -1587,8 +1589,12 @@ idnode_notify ( idnode_t *in, const char *action )
     return;
 
   while (ic) {
-    if (ic->ic_event)
-      notify_delayed(uuid, ic->ic_event, action);
+    if (ic->ic_event) {
+      if (!ic->ic_snode)
+        notify_delayed(uuid, ic->ic_event, action);
+      else
+        notify_reload(ic->ic_event);
+    }
     ic = ic->ic_super;
   }
 }
index de0ece45d365941790eb3f8610cfa54827e31c74..d545ea9a2f5888bccf9166c50244f02137fe788b 100644 (file)
@@ -64,6 +64,7 @@ struct idclass {
   const property_t       *ic_properties;   ///< Property list
   const char             *ic_event;        ///< Events to fire on add/delete/title
   uint32_t                ic_perm_def;     ///< Default permissions
+  idnode_t               *ic_snode;        ///< Simple node
 
   /* Callbacks */
   idnode_set_t   *(*ic_get_childs) (idnode_t *self);
index b802e319c362b2e354c337c7fcb40242cb33e9e8..e917386ea6beb0a4fa305d91813738115dd8d072 100644 (file)
@@ -63,33 +63,46 @@ static RB_HEAD(,imagecache_image)     imagecache_by_url;
 SKEL_DECLARE(imagecache_skel, imagecache_image_t);
 
 #if ENABLE_IMAGECACHE
-struct imagecache_config imagecache_conf;
-static const property_t  imagecache_props[] = {
-  {
-    .type   = PT_BOOL,
-    .id     = "enabled",
-    .name   = N_("Enabled"),
-    .off    = offsetof(struct imagecache_config, enabled),
-  },
-  {
-    .type   = PT_BOOL,
-    .id     = "ignore_sslcert",
-    .name   = N_("Ignore invalid SSL certificate"),
-    .off    = offsetof(struct imagecache_config, ignore_sslcert),
-  },
-  {
-    .type   = PT_U32,
-    .id     = "ok_period",
-    .name   = N_("Re-try period"),
-    .off    = offsetof(struct imagecache_config, ok_period),
-  },
-  {
-    .type   = PT_U32,
-    .id     = "fail_period",
-    .name   = N_("Re-try period of failed images"),
-    .off    = offsetof(struct imagecache_config, fail_period),
-  },
-  {}
+struct imagecache_config imagecache_conf = {
+  .idnode.in_class = &imagecache_class,
+};
+
+static void imagecache_save(idnode_t *self);
+
+const idclass_t imagecache_class = {
+  .ic_snode      = (idnode_t *)&imagecache_conf,
+  .ic_class      = "imagecache",
+  .ic_caption    = N_("Image Cache"),
+  .ic_event      = "imagecache",
+  .ic_perm_def   = ACCESS_ADMIN,
+  .ic_save       = imagecache_save,
+  .ic_properties = (const property_t[]){
+    {
+      .type   = PT_BOOL,
+      .id     = "enabled",
+      .name   = N_("Enabled"),
+      .off    = offsetof(struct imagecache_config, enabled),
+    },
+    {
+      .type   = PT_BOOL,
+      .id     = "ignore_sslcert",
+      .name   = N_("Ignore invalid SSL certificate"),
+      .off    = offsetof(struct imagecache_config, ignore_sslcert),
+    },
+    {
+      .type   = PT_U32,
+      .id     = "ok_period",
+      .name   = N_("Re-fetch period"),
+      .off    = offsetof(struct imagecache_config, ok_period),
+    },
+    {
+      .type   = PT_U32,
+      .id     = "fail_period",
+      .name   = N_("Re-try period (hours)"),
+      .off    = offsetof(struct imagecache_config, fail_period),
+    },
+    {}
+  }
 };
 
 static pthread_cond_t                 imagecache_cond;
@@ -319,7 +332,7 @@ imagecache_init ( void )
   /* Load settings */
 #if ENABLE_IMAGECACHE
   if ((m = hts_settings_load("imagecache/config"))) {
-    imagecache_set_config(m);
+    idnode_load(&imagecache_conf.idnode, m);
     htsmsg_destroy(m);
   }
 #endif
@@ -401,38 +414,17 @@ imagecache_done ( void )
 
 #if ENABLE_IMAGECACHE
 
-/*
- * Get config
- */
-htsmsg_t *
-imagecache_get_config ( void )
-{
-  htsmsg_t *m = htsmsg_create_map();
-  prop_read_values(&imagecache_conf, imagecache_props, m, NULL, 0, NULL);
-  return m;
-}
-
-/*
- * Set config
- */
-int
-imagecache_set_config ( htsmsg_t *m )
-{
-  int save = prop_write_values(&imagecache_conf, imagecache_props, m, 0, NULL);
-  if (save)
-    pthread_cond_broadcast(&imagecache_cond);
-  return save;
-}
-
 /*
  * Save
  */
-void
-imagecache_save ( void )
+static void
+imagecache_save ( idnode_t *self )
 {
-  htsmsg_t *m = imagecache_get_config();
-  hts_settings_save(m, "imagecache/config");
-  notify_reload("imagecache");
+  htsmsg_t *c = htsmsg_create_map();
+  idnode_save(&imagecache_conf.idnode, c);
+  hts_settings_save(c, "imagecache/config");
+  htsmsg_destroy(c);
+  pthread_cond_broadcast(&imagecache_cond);
 }
 
 /*
index 4d9945b1016384896b08fcdebf1740d6031f91c9..20e289b3e5eb17a41f235bd8cb31424504acd205 100644 (file)
 #define __IMAGE_CACHE_H__
 
 #include <pthread.h>
+#include "idnode.h"
 
 struct imagecache_config {
-  int       __unused__;   // to avoid assert in prop.c (first member should be idnode_t)
+  idnode_t  idnode;
   int       enabled;
   int       ignore_sslcert;
   uint32_t  ok_period;
@@ -30,16 +31,14 @@ struct imagecache_config {
 };
 
 extern struct imagecache_config imagecache_conf;
+extern const idclass_t imagecache_class;
 
 extern pthread_mutex_t imagecache_mutex;
 
 void     imagecache_init     ( void );
 void    imagecache_done     ( void );
 
-htsmsg_t *imagecache_get_config ( void );
-int       imagecache_set_config ( htsmsg_t *c );
-void      imagecache_save       ( void );
-void      imagecache_clean      ( void );
+void     imagecache_clean    ( void );
 
 // Note: will return 0 if invalid (must serve original URL)
 uint32_t imagecache_get_id  ( const char *url );
index f91f4222ad7ee49f2461dc6451169f99a378f75e..2f56e749657e8132c339832d68e1efc4c4e1965a 100644 (file)
@@ -263,107 +263,35 @@ tvheadend.imgcacheconf = function(panel, index) {
     if (tvheadend.capabilities.indexOf('imagecache') === -1)
         return;
 
-    var imagecache_reader = new Ext.data.JsonReader({root: 'entries'},
-        [ 'enabled', 'ok_period', 'fail_period', 'ignore_sslcert' ]);
-
-    var imagecacheEnabled = new Ext.ux.form.XCheckbox({
-        name: 'enabled',
-        fieldLabel: _('Enabled')
-    });
-
-    var imagecacheOkPeriod = new Ext.form.NumberField({
-        name: 'ok_period',
-        fieldLabel: _('Re-fetch period (hours)')
-    });
-
-    var imagecacheFailPeriod = new Ext.form.NumberField({
-        name: 'fail_period',
-        fieldLabel: _('Re-try period (hours)')
-    });
-
-    var imagecacheIgnoreSSLCert = new Ext.ux.form.XCheckbox({
-        name: 'ignore_sslcert',
-        fieldLabel: _('Ignore invalid SSL certificate')
-    });
-
-    var imagecachePanel = new Ext.form.FieldSet({
-        title: _('Image Caching'),
-        width: 700,
-        autoHeight: true,
-        collapsible: true,
-        animCollapse: true,
-        items: [imagecacheEnabled, imagecacheOkPeriod, imagecacheFailPeriod,
-            imagecacheIgnoreSSLCert]
-    });
-
-    var imagecache_form = new Ext.form.FormPanel({
-        border: false,
-        labelAlign: 'left',
-        labelWidth: 300,
-        waitMsgTarget: true,
-        reader: imagecache_reader,
-        layout: 'form',
-        defaultType: 'textfield',
-        autoHeight: true,
-        items: [imagecachePanel]
-    });
-
-    var saveButton = new Ext.Button({
-        text: _("Save configuration"),
-        tooltip: _('Save changes made to configuration below'),
-        iconCls: 'save',
-        handler: saveChanges
-    });
-
-    var imagecacheButton = new Ext.Button({
-        text: _("Clean image (icon) cache"),
-        tooltip: _('Clean image cache on storage'),
-        iconCls: 'clean',
-        handler: cleanImagecache
-    });
-
-    var _tbar = [saveButton, '-', imagecacheButton];
+    var cleanButton = {
+        name: 'clean',
+        builder: function() {
+            return new Ext.Toolbar.Button({
+                tooltip: _('Clean image cache on storage'),
+                iconCls: 'clean',
+                text: _('Clean image (icon) cache')
+            });
+        },
+        callback: function(conf) {
+            tvheadend.Ajax({
+               url: 'api/imagecache/config/clean',
+               params: { clean: 1 },
+            });
+        }
+    };
 
-    var mpanel = new Ext.Panel({
+    tvheadend.idnode_simple(panel, {
+        url: 'api/imagecache/config',
         title: _('Image cache'),
         iconCls: 'imgcacheconf',
-        border: false,
-        autoScroll: true,
-        bodyStyle: 'padding:15px',
-        layout: 'form',
-        items: [imagecache_form],
-        tbar: _tbar
-    });
-
-    tvheadend.paneladd(panel, mpanel, index);
-
-    mpanel.on('render', function() {
-        imagecache_form.getForm().load({
-            url: 'api/imagecache/config/load',
-            success: function(form, action) {
-                imagecache_form.enable();
-            },
-            failure: function(form, action) {
-                alert(_("FAILED"));
-            }
-        });
+        tabIndex: index,
+        comet: 'imagecache',
+        tbar: [cleanButton],
+        help: function() {
+            new tvheadend.help(_('General Configuration'), 'config_general.html');
+        }
     });
 
-    function saveChanges(params) {
-        if (imagecache_form)
-            imagecache_form.getForm().submit({
-                url: 'api/imagecache/config/save',
-                params: params || {},
-                waitMsg: _('Saving data...'),
-                failure: function(form, action) {
-                    Ext.Msg.alert(_('Imagecache save failed'), action.result.errormsg);
-                }
-            });
-    }
-
-    function cleanImagecache() {
-        saveChanges({'clean': 1});
-    }
 };
 
 /*
index bbe4a59ac9d0717d91fd05a16b4ea72cd6e3edca..f6c9ec10f05b9e1b1004020883cd91fded17d75d 100644 (file)
@@ -2031,3 +2031,189 @@ tvheadend.idnode_tree = function(panel, conf)
     tvheadend.paneladd(panel, dpanel, conf.tabIndex);
     tvheadend.panelreg(panel, dpanel, builder, destroyer);
 };
+
+/*
+ * Simple Node Editor
+ */
+tvheadend.idnode_simple = function(panel, conf)
+{
+    var mpanel = null;
+    var update_cb = null;
+
+    function update() {
+        if (update_cb)
+            update_cb(1);
+    }
+
+    function builder() {
+        if (mpanel)
+            return;
+
+        if (conf.builder)
+            conf.builder(conf);
+
+        var buttons = [];
+        var abuttons = {};
+        var current = null;
+
+        /* Top bar */
+        abuttons.save = new Ext.Toolbar.Button({
+            tooltip: _('Save pending changes (marked with red border)'),
+            iconCls: 'save',
+            text: _('Save'),
+            disabled: true,
+            handler: function() {
+                var node = current.getForm().getFieldValues();
+                tvheadend.Ajax({
+                    url: conf.url + '/save',
+                    params: {
+                        node: Ext.encode(node)
+                    },
+                    success: function() {
+                        roweditor_destroy();
+                        form_load(true);
+                    }
+                });
+            }
+        });
+        buttons.push(abuttons.save);
+        abuttons.undo = new Ext.Toolbar.Button({
+            tooltip: _('Revert pending changes (marked with red border)'),
+            iconCls: 'undo',
+            text: _('Undo'),
+            disabled: true,
+            handler: function() {
+                if (current)
+                    current.getForm().reset();
+            }
+        });
+        buttons.push(abuttons.undo);
+
+        /* Extra buttons */
+        if (conf.tbar) {
+            buttons.push('-');
+            for (i = 0; i < conf.tbar.length; i++) {
+                var t = conf.tbar[i];
+                if (t.name && t.builder) {
+                    var b = t.builder();
+                    if (t.callback) {
+                        b.callback = t.callback;
+                        b.handler = function(b, e) {
+                            this.callback(this, e);
+                        }
+                    }
+                    abuttons[t.name] = b;
+                    buttons.push(b);
+                } else if (t.name)
+                    buttons.push(t.name);
+            }
+        }
+
+        /* Help */
+        if (conf.help) {
+            buttons.push('->');
+            buttons.push({
+                text: _('Help'),
+                iconCls: 'help',
+                handler: conf.help
+            });
+        }
+
+        function form_build(d) {
+
+            fpanel = new Ext.form.FormPanel({
+                //title: conf.title || null,
+                frame: true,
+                border: true,
+                bodyStyle: 'padding: 5px',
+                labelAlign: 'left',
+                labelWidth: conf.labelWidth || 300,
+                autoWidth: false,
+                autoHeight: false,
+                width: conf.nowidth ? null : (conf.width || 730),
+                defaultType: 'textfield',
+                buttonAlign: 'left',
+                autoScroll: true
+            });
+
+            tvheadend.idnode_editor_form(d.props || d.params, d.meta,
+                                         fpanel, { showpwd: conf.showpwd });
+
+            return fpanel;
+
+        }
+
+        function form_destroy() {
+            if (current)
+                mpanel.remove(current);
+            current = null;
+        }
+
+        function form_load(force) {
+            if (!force && current)
+                return;
+            var params = conf.edit ? (conf.edit.params || {}) : {};
+            params.uuid = r.id;
+            params.meta = 1;
+            tvheadend.Ajax({
+                url: conf.url + '/load',
+                params: params,
+                success: function(d) {
+                    d = json_decode(d);
+                    form_destroy();
+                    current = new form_build(d[0]);
+                    abuttons.save.setDisabled(false);
+                    abuttons.undo.setDisabled(false);
+                    if (abuttons.del)
+                      abuttons.del.setDisabled(false);
+                    mpanel.add(current);
+                    mpanel.doLayout();
+                }
+            });
+        }
+
+        var mpanel = new Ext.Panel({
+            tbar: buttons,
+            layout: 'hbox',
+            padding: 5,
+            border: false,
+            layoutConfig: {
+               align: 'stretch'
+            }
+        });
+        
+        dpanel.add(mpanel);
+        dpanel.doLayout(false, true);
+
+        if (conf.comet) {
+            update_cb = form_load;
+            tvheadend.comet.on(conf.comet, update);
+        }
+
+        form_load(true);
+    }
+
+    function destroyer() {
+        if (mpanel === null || !tvheadend.dynamic)
+            return;
+        if (conf.comet) {
+            update_cb = null;
+            tvheadend.comet.un(conf.comet, update);
+        }
+        dpanel.removeAll(true);
+        mpanel = null;
+        if (conf.destroyer)
+            conf.destroyer(conf);
+    }
+
+    var dpanel = new Ext.Panel({
+        border: false,
+        header: false,
+        layout: 'fit',
+        title: conf.title || '',
+        iconCls: conf.iconCls || ''
+    });
+
+    tvheadend.paneladd(panel, dpanel, conf.tabIndex);
+    tvheadend.panelreg(panel, dpanel, builder, destroyer);
+};