From: Jaroslav Kysela Date: Thu, 10 Sep 2015 12:12:04 +0000 (+0200) Subject: idnode, imagecache: introduce simple node system X-Git-Tag: v4.2.1~2137 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc7b753b097d455faf81d469fc20842d04a96b79;p=thirdparty%2Ftvheadend.git idnode, imagecache: introduce simple node system --- diff --git a/src/api.h b/src/api.h index 81a33ab34..c1d30da28 100644 --- 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 */ diff --git a/src/api/api_idnode.c b/src/api/api_idnode.c index bfc3d16f9..43d8c31b3 100644 --- a/src/api/api_idnode.c +++ b/src/api/api_idnode.c @@ -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 ) diff --git a/src/api/api_imagecache.c b/src/api/api_imagecache.c index d8accb6ba..9ddd2f759 100644 --- a/src/api/api_imagecache.c +++ b/src/api/api_imagecache.c @@ -26,33 +26,17 @@ #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 }, }; diff --git a/src/idnode.c b/src/idnode.c index b08348e83..e9b160f17 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -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; } } diff --git a/src/idnode.h b/src/idnode.h index de0ece45d..d545ea9a2 100644 --- a/src/idnode.h +++ b/src/idnode.h @@ -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); diff --git a/src/imagecache.c b/src/imagecache.c index b802e319c..e917386ea 100644 --- a/src/imagecache.c +++ b/src/imagecache.c @@ -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); } /* diff --git a/src/imagecache.h b/src/imagecache.h index 4d9945b10..20e289b3e 100644 --- a/src/imagecache.h +++ b/src/imagecache.h @@ -20,9 +20,10 @@ #define __IMAGE_CACHE_H__ #include +#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 ); diff --git a/src/webui/static/app/config.js b/src/webui/static/app/config.js index f91f4222a..2f56e7496 100644 --- a/src/webui/static/app/config.js +++ b/src/webui/static/app/config.js @@ -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}); - } }; /* diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index bbe4a59ac..f6c9ec10f 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -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); +};